Libraries

First we’ll load in the packages we need to tidy & analyze our simulation results.

library(dplyr)                 # data manipulation 
library(scran)                 # scRNA tools 
library(scater)                # more scRNA tools
library(Seurat)                # scRNA methods & data structures
library(ggplot2)               # plots
library(targets)               # pipeline tools
library(paletteer)             # plot colors 
library(patchwork)             # plot combination
library(SingleCellExperiment)  # scRNA data structures 

Lung Reference

First we’ll load in the lung reference dataset from the {scRNAseq} package & process it like we would our simulated datasets. Please ignore the calls to gc() littered throughout the code, it was so difficult to get all these datasets into memory & processed without my R session crashing over & over again.

lung_data <- scRNAseq::ZilionisLungData(which = "human", filter = TRUE)
lung_data_clean <- lung_data[rowSums(counts(lung_data) > 0) >= 3, ]  # genes in at least 3 cells
colnames(lung_data_clean) <- make.unique(colnames(lung_data_clean))
gc(full = TRUE)
# process 
lung_data_clean <- logNormCounts(lung_data_clean)
var_decomp <- modelGeneVar(lung_data_clean)
top2k_hvgs <- getTopHVGs(var_decomp, n = 2000)
gc(full = TRUE)
lung_data_clean <- runPCA(lung_data_clean, subset_row = top2k_hvgs)
reducedDim(lung_data_clean, "PCAsub") <- reducedDim(lung_data_clean, "PCA")[, 1:30, drop = FALSE]
lung_data_clean <- runUMAP(lung_data_clean, 
                           dimred = "PCAsub", 
                           n_dimred = 1:30)
gc(full = TRUE)
g <- buildSNNGraph(lung_data_clean, 
                   use.dimred = "PCAsub", 
                   k = 30)
clusters <- igraph::cluster_louvain(graph = g)$membership
colLabels(lung_data_clean) <- factor(clusters)
gc(full = TRUE)
lung_data_clean <- as.Seurat(lung_data_clean, 
                             counts = "counts", 
                             data = "logcounts")
gc(full = TRUE)

Next we create a table of summary statistics & dataset characteristics. We then create a {ggplot2}-friendly version of the table using {gridExtra} & {gtable}. This allows us to include the table as part of a plot object.

n_cells <- ncol(lung_data_clean)
n_genes <- nrow(lung_data_clean)
mean_count <- mean(lung_data_clean@assays$originalexp@counts)
med_count <- 0
sd_count <- sd(lung_data_clean@assays$originalexp@counts)
var_count <- sd_count^2  # faster 
range_count <- range(lung_data_clean@assays$originalexp@counts)
sparsity_count <- mean(lung_data_clean@assays$originalexp@counts == 0)
summary_df <- data.frame(metric = c("Mean", "Median", "S.D.", "Variance", "Range", "Sparsity"), 
                         value = c(round(mean_count, 2), 
                                   med_count, 
                                   round(sd_count, 2), 
                                   round(var_count, 2),
                                   paste0("(", range_count[1], ", ", range_count[2], ")"), 
                                   paste0(round(sparsity_count, 4) * 100, "%")))
plot_table <- gridExtra::tableGrob(summary_df, 
                                   rows = NULL, 
                                   cols = c("Metric", "Value"), 
                                   theme = gridExtra::ttheme_minimal(core = list(fg_params = list(hjust = 0, x = 0.05)), 
                                                                     colhead = list(fg_params = list(hjust = 0, x = 0.05)))) %>% 
              gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                      t = 2, 
                                      b = nrow(.), 
                                      l = 1, 
                                      r = ncol(.)) %>% 
              gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                      t = 1, 
                                      l = 1, 
                                      r = ncol(.))
gc(full = TRUE)

The first plot we want is a histogram of raw counts. Unfortunately, this is apparently very hard to do since our data is so large. We first convert our counts matrix to a file-backed matrix, and manually compute the bin ranges we’re interested in (with \(b = 20\) bins). Next, we iterate over the bins & sum the number of counts within each range. However, this too cause issues, so we also iterate over the columns of the matrix (cells), and sum within each cell first, then aggregate afterwards for the final per-bin value.

bin_df <- data.frame(lower_bound = seq(range_count[1], range_count[2], length.out = 20)) %>% 
          mutate(upper_bound = lead(lower_bound), 
                 across(contains("bound"), \(x) round(x))) %>% 
          filter(!is.na(upper_bound)) %>% 
          mutate(n = NA_real_)
counts_mat <- bigstatsr::as_FBM(as.matrix(lung_data_clean@assays$originalexp@counts),
                                type = "integer",
                                is_read_only = TRUE)
gc(full = TRUE)
for (b in seq(nrow(bin_df))) {
  bin_sums_tmp <- vector("numeric", length = ncol(counts_mat))
  for (j in seq(ncol(counts_mat))) {
    col_j <- counts_mat[, j]
    if (b == 1) {
      bin_sums_tmp[j] <- sum(col_j >= bin_df$lower_bound[b] & col_j <= bin_df$upper_bound[b])
    } else {
      bin_sums_tmp[j] <- sum(col_j > bin_df$lower_bound[b] & col_j <= bin_df$upper_bound[b])
    }
    rm(col_j)
  }
  bin_sum <- sum(bin_sums_tmp)
  bin_df$n[b] <- bin_sum
  gc(full = TRUE)
}
rm(counts_mat); gc(full = TRUE)
p0 <- ggplot(bin_df, aes(x = lower_bound, y = n)) + 
      geom_bar(stat = "identity", fill = "dodgerblue") + 
      scale_y_continuous(labels = scales::label_scientific()) + 
      scale_x_continuous(labels = scales::label_comma()) + 
      labs(x = "Raw Expression", y = "Frequency") + 
      theme_classic(base_size = 14)

We’ll do the same for the normalized counts.

bin_df <- data.frame(lower_bound = seq(0, max(lung_data_clean@assays$originalexp@data), length.out = 20)) %>% 
          mutate(upper_bound = lead(lower_bound)) %>% 
          filter(!is.na(upper_bound)) %>% 
          mutate(n = NA_real_)
counts_mat <- bigstatsr::as_FBM(as.matrix(lung_data_clean@assays$originalexp@data),
                                type = "float",
                                is_read_only = TRUE)
gc(full = TRUE)
for (b in seq(nrow(bin_df))) {
  bin_sums_tmp <- vector("numeric", length = ncol(counts_mat))
  for (j in seq(ncol(counts_mat))) {
    col_j <- counts_mat[, j]
    if (b == 1) {
      bin_sums_tmp[j] <- sum(col_j >= bin_df$lower_bound[b] & col_j <= bin_df$upper_bound[b])
    } else {
      bin_sums_tmp[j] <- sum(col_j > bin_df$lower_bound[b] & col_j <= bin_df$upper_bound[b])
    }
    rm(col_j)
  }
  bin_sum <- sum(bin_sums_tmp)
  bin_df$n[b] <- bin_sum
  gc(full = TRUE)
}
rm(counts_mat); gc(full = TRUE)
p1 <- ggplot(bin_df, aes(x = lower_bound, y = n)) + 
      geom_bar(stat = "identity", fill = "forestgreen") + 
      scale_y_continuous(labels = scales::label_scientific()) + 
      labs(x = "Normalized Expression", y = "Frequency") + 
      theme_classic(base_size = 14)

Now that the tricky stuff is out of the way, we make a UMAP & PCA plot of the unsupervised clustering.

p2 <- data.frame(UMAP1 = lung_data_clean@reductions$UMAP@cell.embeddings[, 1], 
                 UMAP2 = lung_data_clean@reductions$UMAP@cell.embeddings[, 2], 
                 cluster = lung_data_clean$label) %>% 
      ggplot(aes(x = UMAP1, y = UMAP2, color = cluster)) + 
      geom_point() + 
      scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
      labs(x = "UMAP 1", y = "UMAP 2", color = "Louvain Cluster") + 
      theme_classic(base_size = 14) + 
      theme(axis.text = element_blank(), 
            axis.ticks = element_blank()) + 
      guides(color = guide_legend(override.aes = list(size = 2)))
p3 <- data.frame(PC1 = lung_data_clean@reductions$PCA@cell.embeddings[, 1], 
                 PC2 = lung_data_clean@reductions$PCA@cell.embeddings[, 2], 
                 cluster = lung_data_clean$label) %>% 
      ggplot(aes(x = PC1, y = PC2, color = cluster)) + 
      geom_point() + 
      scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
      labs(x = "PC 1", y = "PC 2", color = "Louvain Cluster") + 
      theme_classic(base_size = 14) + 
      theme(legend.position = "none", 
            axis.text = element_blank(), 
            axis.ticks = element_blank())

We align everything using the {patchwork} package, save the figure, and plot it all.

p4a <- (p0 | p1) / (p2 | p3) + 
       plot_layout(guides = "collect")
p4b <- (p4a | plot_table) + 
       plot_layout(ncol = 2, widths = c(3, 1)) + 
       plot_annotation(title = paste0("Metrics for Lung Reference Dataset"))
ggsave(filename = "QC_lung_reference.pdf",
       plot = p4b, 
       device = "pdf", 
       path = "/blue/rbacher/j.leary/repos/scLANE_Analysis/Figures/QC_Plots/", 
       width = 13,
       height = 8, 
       units = "in", 
       dpi = "retina")
p4b

Single-subject

We load the single-subject simulated datasets into a list & turn them into {Seurat} objects for plotting.

# 100 cells 
tar_load(lung_sim_DEG_01_CELLS_100)
tar_load(lung_sim_DEG_05_CELLS_100)
tar_load(lung_sim_DEG_10_CELLS_100)
tar_load(lung_sim_DEG_20_CELLS_100)
# 500 cells 
tar_load(lung_sim_DEG_01_CELLS_500)
tar_load(lung_sim_DEG_05_CELLS_500)
tar_load(lung_sim_DEG_10_CELLS_500)
tar_load(lung_sim_DEG_20_CELLS_500)
# 1,000 cells 
tar_load(lung_sim_DEG_01_CELLS_1000)
tar_load(lung_sim_DEG_05_CELLS_1000)
tar_load(lung_sim_DEG_10_CELLS_1000)
tar_load(lung_sim_DEG_20_CELLS_1000)
# 2,500 cells
tar_load(lung_sim_DEG_01_CELLS_2500)
tar_load(lung_sim_DEG_05_CELLS_2500)
tar_load(lung_sim_DEG_10_CELLS_2500)
tar_load(lung_sim_DEG_20_CELLS_2500)
# 5,000 cells 
tar_load(lung_sim_DEG_01_CELLS_5000)
tar_load(lung_sim_DEG_05_CELLS_5000)
tar_load(lung_sim_DEG_10_CELLS_5000)
tar_load(lung_sim_DEG_20_CELLS_5000)
# coerce to list & process
obj_list <- purrr::map(ls(pattern = "lung_sim")[!grepl("balanced", ls(pattern = "lung_sim"))], function(sim) {
  obj <- eval(as.symbol(sim))
  obj <- as.Seurat(obj, counts = "counts", data = "logcounts")
  obj@meta.data <- mutate(obj@meta.data, 
                          perc_deg = paste0(as.numeric(stringr::str_remove(stringr::str_extract(sim, "lung_sim_DEG_.."), "lung_sim_DEG_")), "%"), 
                          n_cells = as.character(ncol(obj)), 
                          n_genes = as.character(nrow(obj)), 
                          sce_name = sim)
  return(obj)
})
rm(list = ls(pattern = "lung_sim")); gc(full = TRUE)

Iterating over the datasets, we print each QC plot & save them to PDFs.

purrr::walk(obj_list, function(z) {
  # gather metadata 
  obj_name <- z@meta.data$sce_name[1]
  n_cells <- z@meta.data$n_cells[1]
  n_genes <- z@meta.data$n_genes[1]
  perc_deg <- z@meta.data$perc_deg[1]
  # summary stat table 
  sparsity_count <- mean(z@assays$originalexp@counts == 0)
  mean_count <- mean(z@assays$originalexp@counts)
  med_count <- ifelse(sparsity_count > 0.5, 0, median(z@assays$originalexp@counts))
  sd_count <- sd(z@assays$originalexp@counts)
  var_count <- sd_count^2 
  range_count <- range(z@assays$originalexp@counts)
  summary_df <- data.frame(metric = c("Mean", "Median", "S.D.", "Variance", "Range", "Sparsity"), 
                           value = c(round(mean_count, 2), 
                                     round(med_count, 2), 
                                     round(sd_count, 2), 
                                     round(var_count, 2),
                                     paste0("(", range_count[1], ", ", range_count[2], ")"), 
                                     paste0(round(sparsity_count, 4) * 100, "%")))
  # create counts histogram
  p0 <- data.frame(x = as.numeric(z@assays$originalexp@counts)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "dodgerblue") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        scale_x_continuous(labels = scales::label_comma()) + 
        labs(x = "Raw Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create log counts histogram
  p1 <- data.frame(x = as.numeric(z@assays$originalexp@data)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "forestgreen") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        labs(x = "Normalized Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create UMAP by cluster 
  p2 <- data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                   UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                   cluster = z$label) %>% 
        ggplot(aes(x = UMAP1, y = UMAP2, color = cluster)) + 
        geom_point() + 
        scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
        labs(x = "UMAP 1", y = "UMAP 2", color = "Louvain Cluster") + 
        theme_classic(base_size = 14) + 
        theme(axis.text = element_blank(), 
              axis.ticks = element_blank()) + 
        guides(color = guide_legend(override.aes = list(size = 2)))
  # create PCA of cell ordering 
  p3 <- data.frame(PC1 = z@reductions$PCA@cell.embeddings[, 1], 
                   PC2 = z@reductions$PCA@cell.embeddings[, 2], 
                   cell_time = z$cell_time_normed) %>% 
        ggplot(aes(x = PC1, y = PC2, color = cell_time)) + 
        geom_point() + 
        scale_color_gradientn(colors = paletteer_d("wesanderson::Zissou1")) + 
        labs(x = "PC 1", y = "PC 2", color = "True Ordering") + 
        theme_classic(base_size = 14) + 
        theme(axis.text = element_blank(), 
              axis.ticks = element_blank())
  # table of simulation parameters 
  param_df <- data.frame(metric = c("Number of Cells", "Number of Genes", "% Dynamic Genes"), 
                         value = c(as.character(n_cells), as.character(n_genes), perc_deg))
  plot_table <- rbind(summary_df, param_df) %>% 
                gridExtra::tableGrob(rows = NULL, 
                                     cols = c("Metric", "Value"), 
                                     theme = gridExtra::ttheme_minimal(core = list(fg_params = list(hjust = 0, x = 0.05)), 
                                                                       colhead = list(fg_params = list(hjust = 0, x = 0.05)))) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 2, 
                                        b = nrow(.), 
                                        l = 1, 
                                        r = ncol(.)) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 1, 
                                        l = 1, 
                                        r = ncol(.))
  # align everything 
  p4a <- (p0 | p1) / (p2 | p3) + 
         plot_layout(guides = "collect")
  p4b <- (p4a | plot_table) + 
         plot_layout(ncol = 2, widths = c(3, 1)) + 
         plot_annotation(title = paste0("Metrics for dataset: ", obj_name))
  # save & print plot
  ggsave(filename = paste0("QC_", obj_name, ".pdf"),
         plot = p4b, 
         device = "pdf", 
         path = "/blue/rbacher/j.leary/repos/scLANE_Analysis/Figures/QC_Plots/", 
         width = 13,
         height = 8, 
         units = "in", 
         dpi = "retina")
  print(p4b)
  # cleanup 
  sink(tempfile())
  rm(p0, p1, p2, p3, p4a, p4b, plot_table); gc(full = TRUE)
  sink()
})

rm(obj_list)

Multi-subject

We repeat the process for the multi-subject simulated datasets.

# 100 cells
tar_load(lung_sim_DEG_10_CELLS_100_balanced)
tar_load(lung_sim_DEG_20_CELLS_100_balanced)
tar_load(lung_sim_DEG_10_CELLS_100_unbalanced)
tar_load(lung_sim_DEG_20_CELLS_100_unbalanced)
# 500 cells
tar_load(lung_sim_DEG_10_CELLS_500_balanced)
tar_load(lung_sim_DEG_20_CELLS_500_balanced)
tar_load(lung_sim_DEG_10_CELLS_500_unbalanced)
tar_load(lung_sim_DEG_20_CELLS_500_unbalanced)
# 1,000 cells 
tar_load(lung_sim_DEG_10_CELLS_1000_balanced)
tar_load(lung_sim_DEG_20_CELLS_1000_balanced)
tar_load(lung_sim_DEG_10_CELLS_1000_unbalanced)
tar_load(lung_sim_DEG_20_CELLS_1000_unbalanced)
# 2,500 cells
tar_load(lung_sim_DEG_10_CELLS_2500_balanced)
tar_load(lung_sim_DEG_20_CELLS_2500_balanced)
tar_load(lung_sim_DEG_10_CELLS_2500_unbalanced)
tar_load(lung_sim_DEG_20_CELLS_2500_unbalanced)
# 5,000 cells 
tar_load(lung_sim_DEG_10_CELLS_5000_balanced)
tar_load(lung_sim_DEG_20_CELLS_5000_balanced)
tar_load(lung_sim_DEG_10_CELLS_5000_unbalanced)
tar_load(lung_sim_DEG_20_CELLS_5000_unbalanced)
# coerce to list & process
obj_list <- purrr::map(ls(pattern = "lung_sim*balanced"), function(sim) {
  obj <- eval(as.symbol(sim))
  obj <- as.Seurat(obj, counts = "counts", data = "logcounts")
  obj@meta.data <- mutate(obj@meta.data, 
                          perc_deg = paste0(as.numeric(stringr::str_remove(stringr::str_extract(sim, "lung_sim_DEG_.."), "lung_sim_DEG_")), "%"), 
                          n_cells = as.character(ncol(obj)), 
                          n_genes = as.character(nrow(obj)), 
                          allocation = ifelse(grepl("_balanced", sim), "Balanced", "Unbalanced"), 
                          sce_name = sim)
  return(obj)
})
rm(list = ls(pattern = "lung_sim")); gc(full = TRUE)

We add a UMAP of the subject identities, & a PCA plot of the true pseudotime split by subject when generating the QC plots for multi-subject data.

purrr::walk(obj_list, function(z) {
  # gather metadata 
  obj_name <- z@meta.data$sce_name[1]
  n_cells <- z@meta.data$n_cells[1]
  n_genes <- z@meta.data$n_genes[1]
  perc_deg <- z@meta.data$perc_deg[1]
  allocation <- z@meta.data$allocation[1]
  n_subjects <- length(unique(z@meta.data$subject))
  # summary stat table 
  sparsity_count <- mean(z@assays$originalexp@counts == 0)
  mean_count <- mean(z@assays$originalexp@counts)
  med_count <- ifelse(sparsity_count > 0.5, 0, median(z@assays$originalexp@counts))
  sd_count <- sd(z@assays$originalexp@counts)
  var_count <- sd_count^2 
  range_count <- range(z@assays$originalexp@counts)
  summary_df <- data.frame(metric = c("Mean", "Median", "S.D.", "Variance", "Range", "Sparsity"), 
                           value = c(round(mean_count, 2), 
                                     round(med_count, 2), 
                                     round(sd_count, 2), 
                                     round(var_count, 2),
                                     paste0("(", range_count[1], ", ", range_count[2], ")"), 
                                     paste0(round(sparsity_count, 4) * 100, "%")))
  # create counts histogram
  p0 <- data.frame(x = as.numeric(z@assays$originalexp@counts)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "dodgerblue") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        scale_x_continuous(labels = scales::label_comma()) + 
        labs(x = "Raw Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create log counts histogram
  p1 <- data.frame(x = as.numeric(z@assays$originalexp@data)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "forestgreen") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        labs(x = "Normalized Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create UMAP by cluster & subject
  legend_clust <- ggpubr::get_legend(p = (data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                                                     UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                                                     cluster = z$label) %>% 
                                          ggplot(aes(x = UMAP1, y = UMAP2, color = cluster)) + 
                                          geom_point() + 
                                          scale_color_manual(values = paletteer_d("ggsci::category20_d3")[1:length(unique(z$label))]) + 
                                          labs(color = "Louvain\nCluster") + 
                                          theme_classic(base_size = 14) + theme(legend.text.align = 0.5)))
                                          
  legend_subj <- ggpubr::get_legend(p = (data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                                                    UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                                                    subject = z$subject) %>% 
                                         ggplot(aes(x = UMAP1, y = UMAP2, color = subject)) + 
                                         geom_point() + 
                                         scale_color_manual(values = paletteer_d("ggsci::category20_d3")[(length(unique(z$label)) + 1):20]) + 
                                         labs(color = "Subject") + 
                                         theme_classic(base_size = 14)))
  p2a <- data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                    UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                    cluster = z$label, 
                    subject = z$subject) %>% 
           tidyr::pivot_longer(cols = c(cluster, subject), names_to = "ident", values_to = "ident_value") %>% 
           mutate(ident = case_when(ident == "cluster" ~ "Louvain Cluster", 
                                    TRUE ~ "Subject")) %>% 
           ggplot(aes(x = UMAP1, y = UMAP2, color = ident_value, group = ident)) + 
           facet_wrap(~ident) + 
           geom_point() + 
           scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
           labs(x = "UMAP 1", y = "UMAP 2") + 
           theme_classic(base_size = 14) + 
           theme(legend.position = "none", 
                 axis.text = element_blank(), 
                 axis.ticks = element_blank())
  p2b <- (p2a + (wrap_elements(legend_clust) / wrap_elements(legend_subj)) + 
         plot_layout(ncol = 2, widths = c(4, 1)))
  # create PCA of cell ordering 
  p3a <- data.frame(PC1 = z@reductions$PCA@cell.embeddings[, 1], 
                    PC2 = z@reductions$PCA@cell.embeddings[, 2], 
                    cell_time = z$cell_time_normed, 
                    subject = z$subject) %>% 
         ggplot(aes(x = PC1, y = PC2, color = cell_time)) + 
         facet_wrap(~subject) + 
         geom_point() + 
         scale_color_gradientn(colors = paletteer_d("wesanderson::Zissou1")) + 
         labs(x = "PC 1", y = "PC 2", color = "True Ordering") + 
         theme_classic(base_size = 14) + 
         theme(axis.ticks = element_blank(), 
               axis.text = element_blank())
  p3b <- data.frame(PC1 = z@reductions$PCA@cell.embeddings[, 1], 
                    PC2 = z@reductions$PCA@cell.embeddings[, 2], 
                    cell_time = z$cell_time_normed) %>% 
       ggplot(aes(x = PC1, y = PC2, color = cell_time)) + 
       geom_point() + 
       scale_color_gradientn(colors = paletteer_d("wesanderson::Zissou1")) + 
       labs(x = "PC 1", y = "PC 2", color = "True Ordering") + 
       theme_classic(base_size = 14) + 
       theme(axis.ticks = element_blank(), 
             axis.text = element_blank())
  p3c <- (p3a | p3b) + 
         plot_layout(guides = "collect", widths = c(3, 2), ncol = 2)
  # table of simulation parameters 
  param_df <- data.frame(metric = c("N Cells", "N Genes", "% Dynamic", "Allocation", "N Subjects"), 
                         value = c(as.character(n_cells), 
                                   as.character(n_genes),
                                   perc_deg, 
                                   allocation, 
                                   n_subjects))
  plot_table <- rbind(summary_df, param_df) %>% 
                gridExtra::tableGrob(rows = NULL, 
                                     cols = c("Metric", "Value"), 
                                     theme = gridExtra::ttheme_minimal(core = list(fg_params = list(hjust = 0, x = 0.05)), 
                                                                       colhead = list(fg_params = list(hjust = 0, x = 0.05)))) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 2, 
                                        b = nrow(.), 
                                        l = 1, 
                                        r = ncol(.)) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 1, 
                                        l = 1, 
                                        r = ncol(.))
  # align everything 
  p4a <- ((p0 | p1 | plot_table) + plot_layout(widths = c(1, 1, 0.5))) / (p2b | p3c) + 
         plot_layout(nrow = 2) + 
         plot_annotation(title = paste0("Metrics for dataset: ", obj_name))
  # save & print plot
  ggsave(filename = paste0("QC_", obj_name, ".pdf"),
         plot = p4a, 
         device = "pdf", 
         path = "/blue/rbacher/j.leary/repos/scLANE_Analysis/Figures/QC_Plots/", 
         width = 13,
         height = 9, 
         units = "in", 
         dpi = "retina")
  print(p4a)
  # cleanup 
  sink(tempfile())
  rm(p0, p1, p2, p3, p4a, plot_table); gc(full = TRUE)
  sink()
})
rm(obj_list)

Pancreas Reference

Next let’s examine the pancreas reference dataset. Since it’s smaller than the lung reference, we don’t need to resort to trickery to generate our histograms.

panc_data <- scRNAseq::BaronPancreasData(which = "human")
panc_data_clean <- panc_data[rowSums(counts(panc_data) > 0) >= 3, ]  # genes in at least 3 cells
panc_data_clean <- logNormCounts(panc_data_clean)
var_decomp <- modelGeneVar(panc_data_clean)
top2k_hvgs <- getTopHVGs(var_decomp, n = 2000)
panc_data_clean <- runPCA(panc_data_clean, subset_row = top2k_hvgs)
reducedDim(panc_data_clean, "PCAsub") <- reducedDim(panc_data_clean, "PCA")[, 1:30, drop = FALSE]
panc_data_clean <- runUMAP(panc_data_clean, 
                           dimred = "PCAsub", 
                           n_dimred = 1:30)
g <- buildSNNGraph(panc_data_clean,
                   use.dimred = "PCAsub",
                   k = 30)
clusters <- igraph::cluster_louvain(graph = g)$membership
colLabels(panc_data_clean) <- factor(clusters)
panc_data_clean <- as.Seurat(panc_data_clean, 
                             counts = "counts", 
                             data = "logcounts")

We re-create the summary statistics table for the pancreas reference.

n_cells <- ncol(panc_data_clean)
n_genes <- nrow(panc_data_clean)
mean_count <- mean(panc_data_clean@assays$originalexp@counts)
med_count <- 0
sd_count <- sd(panc_data_clean@assays$originalexp@counts)
var_count <- sd_count^2  # faster 
range_count <- range(panc_data_clean@assays$originalexp@counts)
sparsity_count <- mean(panc_data_clean@assays$originalexp@counts == 0)
summary_df <- data.frame(metric = c("Mean", "Median", "S.D.", "Variance", "Range", "Sparsity"), 
                         value = c(round(mean_count, 2), 
                                   med_count, 
                                   round(sd_count, 2), 
                                   round(var_count, 2),
                                   paste0("(", range_count[1], ", ", range_count[2], ")"), 
                                   paste0(round(sparsity_count, 4) * 100, "%")))
plot_table <- gridExtra::tableGrob(summary_df, rows = NULL, 
                                   cols = c("Metric", "Value"), 
                                   theme = gridExtra::ttheme_minimal(core = list(fg_params = list(hjust = 0, x = 0.05)), 
                                                                     colhead = list(fg_params = list(hjust = 0, x = 0.05)))) %>% 
              gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                      t = 2, 
                                      b = nrow(.), 
                                      l = 1, 
                                      r = ncol(.)) %>% 
              gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                      t = 1, 
                                      l = 1, 
                                      r = ncol(.))
gc(full = TRUE)

We create the two histograms in the usual way.

p0 <- data.frame(x = as.numeric(panc_data_clean@assays$originalexp@counts)) %>% 
      ggplot(aes(x = x)) + 
      geom_histogram(fill = "dodgerblue") + 
      scale_y_continuous(labels = scales::label_scientific()) + 
      scale_x_continuous(labels = scales::label_comma()) + 
      labs(x = "Raw Expression", y = "Frequency") + 
      theme_classic(base_size = 14)
p1 <- data.frame(x = as.numeric(panc_data_clean@assays$originalexp@data)) %>% 
      ggplot(aes(x = x)) + 
      geom_histogram(fill = "forestgreen") + 
      scale_y_continuous(labels = scales::label_scientific()) + 
      labs(x = "Normalized Expression", y = "Frequency") + 
      theme_classic(base_size = 14)

The UMAP & PCA plots are generated as before.

p2 <- data.frame(UMAP1 = panc_data_clean@reductions$UMAP@cell.embeddings[, 1], 
                 UMAP2 = panc_data_clean@reductions$UMAP@cell.embeddings[, 2], 
                 cluster = panc_data_clean$label) %>% 
      ggplot(aes(x = UMAP1, y = UMAP2, color = cluster)) + 
      geom_point() + 
      scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
      labs(x = "UMAP 1", y = "UMAP 2", color = "Louvain Cluster") + 
      theme_classic(base_size = 14) + 
      theme(axis.text = element_blank(), 
            axis.ticks = element_blank()) +
      guides(color = guide_legend(override.aes = list(size = 2)))
p3 <- data.frame(PC1 = panc_data_clean@reductions$PCA@cell.embeddings[, 1], 
                 PC2 = panc_data_clean@reductions$PCA@cell.embeddings[, 2], 
                 cluster = panc_data_clean$label) %>% 
      ggplot(aes(x = PC1, y = PC2, color = cluster)) + 
      geom_point() + 
      scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
      labs(x = "PC 1", y = "PC 2", color = "Louvain Cluster") + 
      theme_classic(base_size = 14) + 
      theme(legend.position = "none", 
            axis.text = element_blank(), 
            axis.ticks = element_blank())

Lastly, we align everything & plot it.

p4a <- (p0 | p1) / (p2 | p3) + 
       plot_layout(guides = "collect")
p4b <- (p4a | plot_table) + 
       plot_layout(ncol = 2, widths = c(3, 1)) + 
       plot_annotation(title = paste0("Metrics for Pancreas Reference Dataset"))
ggsave(filename = "QC_panc_reference.pdf",
       plot = p4b, 
       device = "pdf", 
       path = "/blue/rbacher/j.leary/repos/scLANE_Analysis/Figures/QC_Plots/", 
       width = 13,
       height = 8, 
       units = "in", 
       dpi = "retina")
p4b

Single-subject

Here we bring the single subject simulations from the pancreas reference into memory.

# 100 cells 
tar_load(panc_sim_DEG_01_CELLS_100)
tar_load(panc_sim_DEG_05_CELLS_100)
tar_load(panc_sim_DEG_10_CELLS_100)
tar_load(panc_sim_DEG_20_CELLS_100)
# 500 cells 
tar_load(panc_sim_DEG_01_CELLS_500)
tar_load(panc_sim_DEG_05_CELLS_500)
tar_load(panc_sim_DEG_10_CELLS_500)
tar_load(panc_sim_DEG_20_CELLS_500)
# 1,000 cells 
tar_load(panc_sim_DEG_01_CELLS_1000)
tar_load(panc_sim_DEG_05_CELLS_1000)
tar_load(panc_sim_DEG_10_CELLS_1000)
tar_load(panc_sim_DEG_20_CELLS_1000)
# 2,500 cells
tar_load(panc_sim_DEG_01_CELLS_2500)
tar_load(panc_sim_DEG_05_CELLS_2500)
tar_load(panc_sim_DEG_10_CELLS_2500)
tar_load(panc_sim_DEG_20_CELLS_2500)
# 5,000 cells 
tar_load(panc_sim_DEG_01_CELLS_5000)
tar_load(panc_sim_DEG_05_CELLS_5000)
tar_load(panc_sim_DEG_10_CELLS_5000)
tar_load(panc_sim_DEG_20_CELLS_5000)
# coerce to list & process
obj_list <- purrr::map(ls(pattern = "panc_sim")[!grepl("balanced", ls(pattern = "panc_sim"))], function(sim) {
  obj <- eval(as.symbol(sim))
  obj <- as.Seurat(obj, counts = "counts", data = "logcounts")
  obj@meta.data <- mutate(obj@meta.data, 
                          perc_deg = paste0(as.numeric(stringr::str_remove(stringr::str_extract(sim, "panc_sim_DEG_.."), "panc_sim_DEG_")), "%"), 
                          n_cells = as.character(ncol(obj)), 
                          n_genes = as.character(nrow(obj)), 
                          sce_name = sim)
  return(obj)
})
rm(list = ls(pattern = "panc_sim")); gc(full = TRUE)

Let’s generate the plots for each dataset.

purrr::walk(obj_list, function(z) {
  # gather metadata 
  obj_name <- z@meta.data$sce_name[1]
  n_cells <- z@meta.data$n_cells[1]
  n_genes <- z@meta.data$n_genes[1]
  perc_deg <- z@meta.data$perc_deg[1]
  # summary stat table 
  sparsity_count <- mean(z@assays$originalexp@counts == 0)
  mean_count <- mean(z@assays$originalexp@counts)
  med_count <- ifelse(sparsity_count > 0.5, 0, median(z@assays$originalexp@counts))
  sd_count <- sd(z@assays$originalexp@counts)
  var_count <- sd_count^2 
  range_count <- range(z@assays$originalexp@counts)
  summary_df <- data.frame(metric = c("Mean", "Median", "S.D.", "Variance", "Range", "Sparsity"), 
                           value = c(round(mean_count, 2), 
                                     round(med_count, 2), 
                                     round(sd_count, 2), 
                                     round(var_count, 2),
                                     paste0("(", range_count[1], ", ", range_count[2], ")"), 
                                     paste0(round(sparsity_count, 4) * 100, "%")))
  # create counts histogram
  p0 <- data.frame(x = as.numeric(z@assays$originalexp@counts)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "dodgerblue") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        scale_x_continuous(labels = scales::label_comma()) + 
        labs(x = "Raw Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create log counts histogram
  p1 <- data.frame(x = as.numeric(z@assays$originalexp@data)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "forestgreen") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        labs(x = "Normalized Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create UMAP by cluster 
  p2 <- data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                   UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                   cluster = z$label) %>% 
        ggplot(aes(x = UMAP1, y = UMAP2, color = cluster)) + 
        geom_point() + 
        scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
        labs(x = "UMAP 1", y = "UMAP 2", color = "Louvain Cluster") + 
        theme_classic(base_size = 14) + 
        theme(axis.text = element_blank(), 
              axis.ticks = element_blank()) + 
        guides(color = guide_legend(override.aes = list(size = 2)))
  # create PCA of cell ordering 
  p3 <- data.frame(PC1 = z@reductions$PCA@cell.embeddings[, 1], 
                   PC2 = z@reductions$PCA@cell.embeddings[, 2], 
                   cell_time = z$cell_time_normed) %>% 
        ggplot(aes(x = PC1, y = PC2, color = cell_time)) + 
        geom_point() + 
        scale_color_gradientn(colors = paletteer_d("wesanderson::Zissou1")) + 
        labs(x = "PC 1", y = "PC 2", color = "True Ordering") + 
        theme_classic(base_size = 14) + 
        theme(axis.text = element_blank(), 
              axis.ticks = element_blank())
  # table of simulation parameters 
  param_df <- data.frame(metric = c("Number of Cells", "Number of Genes", "% Dynamic Genes"), 
                         value = c(as.character(n_cells), as.character(n_genes), perc_deg))
  plot_table <- rbind(summary_df, param_df) %>% 
                gridExtra::tableGrob(rows = NULL, 
                                     cols = c("Metric", "Value"), 
                                     theme = gridExtra::ttheme_minimal(core = list(fg_params = list(hjust = 0, x = 0.05)), 
                                                                       colhead = list(fg_params = list(hjust = 0, x = 0.05)))) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 2, 
                                        b = nrow(.), 
                                        l = 1, 
                                        r = ncol(.)) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 1, 
                                        l = 1, 
                                        r = ncol(.))
  # align everything 
  p4a <- (p0 | p1) / (p2 | p3) + 
         plot_layout(guides = "collect")
  p4b <- (p4a | plot_table) + 
         plot_layout(ncol = 2, widths = c(3, 1)) + 
         plot_annotation(title = paste0("Metrics for dataset: ", obj_name))
  # save & print plot
  ggsave(filename = paste0("QC_", obj_name, ".pdf"),
         plot = p4b, 
         device = "pdf", 
         path = "/blue/rbacher/j.leary/repos/scLANE_Analysis/Figures/QC_Plots/", 
         width = 13,
         height = 8, 
         units = "in", 
         dpi = "retina")
  print(p4b)
  # cleanup 
  sink(tempfile())
  rm(p0, p1, p2, p3, p4a, p4b, plot_table); gc(full = TRUE)
  sink()
})

rm(obj_list)

Multi-subject

We load in the multi-subject simulated datasets from the pancreas reference.

# 100 cells
tar_load(panc_sim_DEG_10_CELLS_100_balanced)
tar_load(panc_sim_DEG_20_CELLS_100_balanced)
tar_load(panc_sim_DEG_10_CELLS_100_unbalanced)
tar_load(panc_sim_DEG_20_CELLS_100_unbalanced)
# 500 cells
tar_load(panc_sim_DEG_10_CELLS_500_balanced)
tar_load(panc_sim_DEG_20_CELLS_500_balanced)
tar_load(panc_sim_DEG_10_CELLS_500_unbalanced)
tar_load(panc_sim_DEG_20_CELLS_500_unbalanced)
# 1,000 cells 
tar_load(panc_sim_DEG_10_CELLS_1000_balanced)
tar_load(panc_sim_DEG_20_CELLS_1000_balanced)
tar_load(panc_sim_DEG_10_CELLS_1000_unbalanced)
tar_load(panc_sim_DEG_20_CELLS_1000_unbalanced)
# 2,500 cells
tar_load(panc_sim_DEG_10_CELLS_2500_balanced)
tar_load(panc_sim_DEG_20_CELLS_2500_balanced)
tar_load(panc_sim_DEG_10_CELLS_2500_unbalanced)
tar_load(panc_sim_DEG_20_CELLS_2500_unbalanced)
# 5,000 cells 
tar_load(panc_sim_DEG_10_CELLS_5000_balanced)
tar_load(panc_sim_DEG_20_CELLS_5000_balanced)
tar_load(panc_sim_DEG_10_CELLS_5000_unbalanced)
tar_load(panc_sim_DEG_20_CELLS_5000_unbalanced)
# coerce to list & process
obj_list <- purrr::map(ls(pattern = "panc_sim_*balanced"), function(sim) {
  obj <- eval(as.symbol(sim))
  obj <- as.Seurat(obj, counts = "counts", data = "logcounts")
  obj@meta.data <- mutate(obj@meta.data, 
                          perc_deg = paste0(as.numeric(stringr::str_remove(stringr::str_extract(sim, "panc_sim_DEG_.."), "panc_sim_DEG_")), "%"), 
                          n_cells = as.character(ncol(obj)), 
                          n_genes = as.character(nrow(obj)), 
                          allocation = ifelse(grepl("_balanced", sim), "Balanced", "Unbalanced"), 
                          sce_name = sim)
  return(obj)
})
rm(list = ls(pattern = "panc_sim")); gc(full = TRUE)

We regenerate the slightly more complex multi-subject figures as we did for the lung dataset.

purrr::walk(obj_list, function(z) {
  # gather metadata 
  obj_name <- z@meta.data$sce_name[1]
  n_cells <- z@meta.data$n_cells[1]
  n_genes <- z@meta.data$n_genes[1]
  perc_deg <- z@meta.data$perc_deg[1]
  allocation <- z@meta.data$allocation[1]
  n_subjects <- length(unique(z@meta.data$subject))
  # summary stat table 
  sparsity_count <- mean(z@assays$originalexp@counts == 0)
  mean_count <- mean(z@assays$originalexp@counts)
  med_count <- ifelse(sparsity_count > 0.5, 0, median(z@assays$originalexp@counts))
  sd_count <- sd(z@assays$originalexp@counts)
  var_count <- sd_count^2 
  range_count <- range(z@assays$originalexp@counts)
  summary_df <- data.frame(metric = c("Mean", "Median", "S.D.", "Variance", "Range", "Sparsity"), 
                           value = c(round(mean_count, 2), 
                                     round(med_count, 2), 
                                     round(sd_count, 2), 
                                     round(var_count, 2),
                                     paste0("(", range_count[1], ", ", range_count[2], ")"), 
                                     paste0(round(sparsity_count, 4) * 100, "%")))
  # create counts histogram
  p0 <- data.frame(x = as.numeric(z@assays$originalexp@counts)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "dodgerblue") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        scale_x_continuous(labels = scales::label_comma()) + 
        labs(x = "Raw Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create log counts histogram
  p1 <- data.frame(x = as.numeric(z@assays$originalexp@data)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "forestgreen") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        labs(x = "Normalized Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create UMAP by cluster & subject
  legend_clust <- ggpubr::get_legend(p = (data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                                                     UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                                                     cluster = z$label) %>% 
                                          ggplot(aes(x = UMAP1, y = UMAP2, color = cluster)) + 
                                          geom_point() + 
                                          scale_color_manual(values = paletteer_d("ggsci::category20_d3")[1:length(unique(z$label))]) + 
                                          labs(color = "Louvain\nCluster") + 
                                          theme_classic(base_size = 14) + theme(legend.text.align = 0.5)))
                                          
  legend_subj <- ggpubr::get_legend(p = (data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                                                    UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                                                    subject = z$subject) %>% 
                                         ggplot(aes(x = UMAP1, y = UMAP2, color = subject)) + 
                                         geom_point() + 
                                         scale_color_manual(values = paletteer_d("ggsci::category20_d3")[(length(unique(z$label)) + 1):20]) + 
                                         labs(color = "Subject") + 
                                         theme_classic(base_size = 14)))
  p2a <- data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                    UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                    cluster = z$label, 
                    subject = z$subject) %>% 
           tidyr::pivot_longer(cols = c(cluster, subject), names_to = "ident", values_to = "ident_value") %>% 
           mutate(ident = case_when(ident == "cluster" ~ "Louvain Cluster", 
                                    TRUE ~ "Subject")) %>% 
           ggplot(aes(x = UMAP1, y = UMAP2, color = ident_value, group = ident)) + 
           facet_wrap(~ident) + 
           geom_point() + 
           scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
           labs(x = "UMAP 1", y = "UMAP 2") + 
           theme_classic(base_size = 14) + 
           theme(legend.position = "none", 
                 axis.text = element_blank(), 
                 axis.ticks = element_blank())
  p2b <- (p2a + (wrap_elements(legend_clust) / wrap_elements(legend_subj)) + 
         plot_layout(ncol = 2, widths = c(4, 1)))
  # create PCA of cell ordering 
  p3a <- data.frame(PC1 = z@reductions$PCA@cell.embeddings[, 1], 
                    PC2 = z@reductions$PCA@cell.embeddings[, 2], 
                    cell_time = z$cell_time_normed, 
                    subject = z$subject) %>% 
         ggplot(aes(x = PC1, y = PC2, color = cell_time)) + 
         facet_wrap(~subject) + 
         geom_point() + 
         scale_color_gradientn(colors = paletteer_d("wesanderson::Zissou1")) + 
         labs(x = "PC 1", y = "PC 2", color = "True Ordering") + 
         theme_classic(base_size = 14) + 
         theme(axis.ticks = element_blank(), 
               axis.text = element_blank())
  p3b <- data.frame(PC1 = z@reductions$PCA@cell.embeddings[, 1], 
                    PC2 = z@reductions$PCA@cell.embeddings[, 2], 
                    cell_time = z$cell_time_normed) %>% 
       ggplot(aes(x = PC1, y = PC2, color = cell_time)) + 
       geom_point() + 
       scale_color_gradientn(colors = paletteer_d("wesanderson::Zissou1")) + 
       labs(x = "PC 1", y = "PC 2", color = "True Ordering") + 
       theme_classic(base_size = 14) + 
       theme(axis.ticks = element_blank(), 
             axis.text = element_blank())
  p3c <- (p3a | p3b) + 
         plot_layout(guides = "collect", widths = c(3, 2), ncol = 2)
  # table of simulation parameters 
  param_df <- data.frame(metric = c("N Cells", "N Genes", "% Dynamic", "Allocation", "N Subjects"), 
                         value = c(as.character(n_cells), 
                                   as.character(n_genes),
                                   perc_deg, 
                                   allocation, 
                                   n_subjects))
  plot_table <- rbind(summary_df, param_df) %>% 
                gridExtra::tableGrob(rows = NULL, 
                                     cols = c("Metric", "Value"), 
                                     theme = gridExtra::ttheme_minimal(core = list(fg_params = list(hjust = 0, x = 0.05)), 
                                                                       colhead = list(fg_params = list(hjust = 0, x = 0.05)))) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 2, 
                                        b = nrow(.), 
                                        l = 1, 
                                        r = ncol(.)) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 1, 
                                        l = 1, 
                                        r = ncol(.))
  # align everything 
  p4a <- ((p0 | p1 | plot_table) + plot_layout(widths = c(1, 1, 0.5))) / (p2b | p3c) + 
         plot_layout(nrow = 2) + 
         plot_annotation(title = paste0("Metrics for dataset: ", obj_name))
  # save & print plot
  ggsave(filename = paste0("QC_", obj_name, ".pdf"),
         plot = p4b, 
         device = "pdf", 
         path = "/blue/rbacher/j.leary/repos/scLANE_Analysis/Figures/QC_Plots/", 
         width = 13,
         height = 9, 
         units = "in", 
         dpi = "retina")
  print(p4a)
  # cleanup 
  sink(tempfile())
  rm(p0, p1, p2a, p2b, p3a, p3b, p3c, p4a, plot_table); gc(full = TRUE)
  sink()
})
rm(obj_list)

Pancreatic Endocrinogenesis Reference

First let’s take a look at the reference dataset itself. Loading this one is a bit more complicated - it comes as part of the scVelo Python library, so we use the {reticulate} R package to access & load it before converting it to a SingleCellExperiment object using the {zellkonverter} package. Though the data comes pre-processed, we reprocess it using the same steps as we did on the other two datasets to ensure a good comparison. A necessary wrinkle here is the activation of a conda environment that I created previously & installed scVelo and its dependencies into.

reticulate::use_condaenv(condaenv = "HPG_venv",
                         conda = "/apps/conda/22.11.1/condabin/conda", 
                         required = TRUE)
scvelo <- reticulate::import("scvelo")
adata <- scvelo$datasets$pancreas()
endo <- zellkonverter::AnnData2SCE(adata = adata)
endo@assays@data$X <- NULL
endo@assays@data$counts <- endo@assays@data$spliced
endo_data_clean <- endo[rowSums(SingleCellExperiment::counts(endo) > 0) >= 3, ]
endo_data_clean <- logNormCounts(endo_data_clean)
var_decomp <- modelGeneVar(endo_data_clean)
top2k_hvgs <- getTopHVGs(var_decomp, n = 2000)
endo_data_clean <- runPCA(endo_data_clean, subset_row = top2k_hvgs)
reducedDim(endo_data_clean, "PCAsub") <- reducedDim(endo_data_clean, "PCA")[, 1:30, drop = FALSE]
endo_data_clean <- runUMAP(endo_data_clean, 
                           dimred = "PCAsub", 
                           n_dimred = 1:30)
g <- buildSNNGraph(endo_data_clean,
                   use.dimred = "PCAsub",
                   k = 30)
clusters <- igraph::cluster_louvain(graph = g)$membership
colLabels(endo_data_clean) <- factor(clusters)
endo_data_clean <- as.Seurat(endo_data_clean, 
                             counts = "counts", 
                             data = "logcounts")

We re-create the summary statistics table for the pancreas reference.

n_cells <- ncol(endo_data_clean)
n_genes <- nrow(endo_data_clean)
mean_count <- mean(endo_data_clean@assays$originalexp@counts)
med_count <- 0
sd_count <- sd(endo_data_clean@assays$originalexp@counts)
var_count <- sd_count^2  # faster 
range_count <- range(endo_data_clean@assays$originalexp@counts)
sparsity_count <- mean(endo_data_clean@assays$originalexp@counts == 0)
summary_df <- data.frame(metric = c("Mean", "Median", "S.D.", "Variance", "Range", "Sparsity"), 
                         value = c(round(mean_count, 2), 
                                   med_count, 
                                   round(sd_count, 2), 
                                   round(var_count, 2),
                                   paste0("(", range_count[1], ", ", range_count[2], ")"), 
                                   paste0(round(sparsity_count, 4) * 100, "%")))
plot_table <- gridExtra::tableGrob(summary_df, rows = NULL, 
                                   cols = c("Metric", "Value"), 
                                   theme = gridExtra::ttheme_minimal(core = list(fg_params = list(hjust = 0, x = 0.05)), 
                                                                     colhead = list(fg_params = list(hjust = 0, x = 0.05)))) %>% 
              gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                      t = 2, 
                                      b = nrow(.), 
                                      l = 1, 
                                      r = ncol(.)) %>% 
              gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                      t = 1, 
                                      l = 1, 
                                      r = ncol(.))
gc(full = TRUE)

Like the pancreas reference, this dataset is small enough that we don’t need to do any trickery to produce histograms.

p0 <- data.frame(x = as.numeric(endo_data_clean@assays$originalexp@counts)) %>% 
      ggplot(aes(x = x)) + 
      geom_histogram(fill = "dodgerblue") + 
      scale_y_continuous(labels = scales::label_scientific()) + 
      scale_x_continuous(labels = scales::label_comma()) + 
      labs(x = "Raw Expression", y = "Frequency") + 
      theme_classic(base_size = 14)
p1 <- data.frame(x = as.numeric(endo_data_clean@assays$originalexp@data)) %>% 
      ggplot(aes(x = x)) + 
      geom_histogram(fill = "forestgreen") + 
      scale_y_continuous(labels = scales::label_scientific()) + 
      labs(x = "Normalized Expression", y = "Frequency") + 
      theme_classic(base_size = 14)

The UMAP & PCA plots are generated as before.

p2 <- data.frame(UMAP1 = endo_data_clean@reductions$UMAP@cell.embeddings[, 1], 
                 UMAP2 = endo_data_clean@reductions$UMAP@cell.embeddings[, 2], 
                 cluster = endo_data_clean$label) %>% 
      ggplot(aes(x = UMAP1, y = UMAP2, color = cluster)) + 
      geom_point() + 
      scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
      labs(x = "UMAP 1", y = "UMAP 2", color = "Louvain Cluster") + 
      theme_classic(base_size = 14) + 
      theme(axis.text = element_blank(), 
            axis.ticks = element_blank()) +
      guides(color = guide_legend(override.aes = list(size = 2)))
p3 <- data.frame(PC1 = endo_data_clean@reductions$PCA@cell.embeddings[, 1], 
                 PC2 = endo_data_clean@reductions$PCA@cell.embeddings[, 2], 
                 cluster = endo_data_clean$label) %>% 
      ggplot(aes(x = PC1, y = PC2, color = cluster)) + 
      geom_point() + 
      scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
      labs(x = "PC 1", y = "PC 2", color = "Louvain Cluster") + 
      theme_classic(base_size = 14) + 
      theme(legend.position = "none", 
            axis.text = element_blank(), 
            axis.ticks = element_blank())
p3

We save the final plot as a PDF & display it.

p4a <- (p0 | p1) / (p2 | p3) + 
       plot_layout(guides = "collect")
p4b <- (p4a | plot_table) + 
       plot_layout(ncol = 2, widths = c(3, 1)) + 
       plot_annotation(title = paste0("Metrics for Endocrinogenesis Reference Dataset"))
ggsave(filename = "QC_endo_reference.pdf",
       plot = p4b, 
       device = "pdf", 
       path = "/blue/rbacher/j.leary/repos/scLANE_Analysis/Figures/QC_Plots/", 
       width = 13,
       height = 8, 
       units = "in", 
       dpi = "retina")
p4b

Single-subject

We load the single-subject endocrinogenesis datasets.

# 100 cells 
tar_load(endo_sim_DEG_01_CELLS_100)
tar_load(endo_sim_DEG_05_CELLS_100)
tar_load(endo_sim_DEG_10_CELLS_100)
tar_load(endo_sim_DEG_20_CELLS_100)
# 500 cells 
tar_load(endo_sim_DEG_01_CELLS_500)
tar_load(endo_sim_DEG_05_CELLS_500)
tar_load(endo_sim_DEG_10_CELLS_500)
tar_load(endo_sim_DEG_20_CELLS_500)
# 1,000 cells 
tar_load(endo_sim_DEG_01_CELLS_1000)
tar_load(endo_sim_DEG_05_CELLS_1000)
tar_load(endo_sim_DEG_10_CELLS_1000)
tar_load(endo_sim_DEG_20_CELLS_1000)
# 2,500 cells
tar_load(endo_sim_DEG_01_CELLS_2500)
tar_load(endo_sim_DEG_05_CELLS_2500)
tar_load(endo_sim_DEG_10_CELLS_2500)
tar_load(endo_sim_DEG_20_CELLS_2500)
# 5,000 cells 
tar_load(endo_sim_DEG_01_CELLS_5000)
tar_load(endo_sim_DEG_05_CELLS_5000)
tar_load(endo_sim_DEG_10_CELLS_5000)
tar_load(endo_sim_DEG_20_CELLS_5000)
# coerce to list & process
obj_list <- purrr::map(ls(pattern = "endo_sim")[!grepl("balanced", ls(pattern = "endo_sim"))], function(sim) {
  obj <- eval(as.symbol(sim))
  obj <- as.Seurat(obj, counts = "counts", data = "logcounts")
  obj@meta.data <- mutate(obj@meta.data, 
                          perc_deg = paste0(as.numeric(stringr::str_remove(stringr::str_extract(sim, "endo_sim_DEG_.."), "endo_sim_DEG_")), "%"), 
                          n_cells = as.character(ncol(obj)), 
                          n_genes = as.character(nrow(obj)), 
                          sce_name = sim)
  return(obj)
})
rm(list = ls(pattern = "endo_sim")); gc(full = TRUE)

We generate the plots for each dataset.

purrr::walk(obj_list, function(z) {
  # gather metadata 
  obj_name <- z@meta.data$sce_name[1]
  n_cells <- z@meta.data$n_cells[1]
  n_genes <- z@meta.data$n_genes[1]
  perc_deg <- z@meta.data$perc_deg[1]
  # summary stat table 
  sparsity_count <- mean(z@assays$originalexp@counts == 0)
  mean_count <- mean(z@assays$originalexp@counts)
  med_count <- ifelse(sparsity_count > 0.5, 0, median(z@assays$originalexp@counts))
  sd_count <- sd(z@assays$originalexp@counts)
  var_count <- sd_count^2 
  range_count <- range(z@assays$originalexp@counts)
  summary_df <- data.frame(metric = c("Mean", "Median", "S.D.", "Variance", "Range", "Sparsity"), 
                           value = c(round(mean_count, 2), 
                                     round(med_count, 2), 
                                     round(sd_count, 2), 
                                     round(var_count, 2),
                                     paste0("(", range_count[1], ", ", range_count[2], ")"), 
                                     paste0(round(sparsity_count, 4) * 100, "%")))
  # create counts histogram
  p0 <- data.frame(x = as.numeric(z@assays$originalexp@counts)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "dodgerblue") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        scale_x_continuous(labels = scales::label_comma()) + 
        labs(x = "Raw Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create log counts histogram
  p1 <- data.frame(x = as.numeric(z@assays$originalexp@data)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "forestgreen") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        labs(x = "Normalized Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create UMAP by cluster 
  p2 <- data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                   UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                   cluster = z$label) %>% 
        ggplot(aes(x = UMAP1, y = UMAP2, color = cluster)) + 
        geom_point() + 
        scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
        labs(x = "UMAP 1", y = "UMAP 2", color = "Louvain Cluster") + 
        theme_classic(base_size = 14) + 
        theme(axis.text = element_blank(), 
              axis.ticks = element_blank()) + 
        guides(color = guide_legend(override.aes = list(size = 2)))
  # create PCA of cell ordering 
  p3 <- data.frame(PC1 = z@reductions$PCA@cell.embeddings[, 1], 
                   PC2 = z@reductions$PCA@cell.embeddings[, 2], 
                   cell_time = z$cell_time_normed) %>% 
        ggplot(aes(x = PC1, y = PC2, color = cell_time)) + 
        geom_point() + 
        scale_color_gradientn(colors = paletteer_d("wesanderson::Zissou1")) + 
        labs(x = "PC 1", y = "PC 2", color = "True Ordering") + 
        theme_classic(base_size = 14) + 
        theme(axis.text = element_blank(), 
              axis.ticks = element_blank())
  # table of simulation parameters 
  param_df <- data.frame(metric = c("Number of Cells", "Number of Genes", "% Dynamic Genes"), 
                         value = c(as.character(n_cells), as.character(n_genes), perc_deg))
  plot_table <- rbind(summary_df, param_df) %>% 
                gridExtra::tableGrob(rows = NULL, 
                                     cols = c("Metric", "Value"), 
                                     theme = gridExtra::ttheme_minimal(core = list(fg_params = list(hjust = 0, x = 0.05)), 
                                                                       colhead = list(fg_params = list(hjust = 0, x = 0.05)))) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 2, 
                                        b = nrow(.), 
                                        l = 1, 
                                        r = ncol(.)) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 1, 
                                        l = 1, 
                                        r = ncol(.))
  # align everything 
  p4a <- (p0 | p1) / (p2 | p3) + 
         plot_layout(guides = "collect")
  p4b <- (p4a | plot_table) + 
         plot_layout(ncol = 2, widths = c(3, 1)) + 
         plot_annotation(title = paste0("Metrics for dataset: ", obj_name))
  # save & print plot
  ggsave(filename = paste0("QC_", obj_name, ".pdf"),
         plot = p4b, 
         device = "pdf", 
         path = "/blue/rbacher/j.leary/repos/scLANE_Analysis/Figures/QC_Plots/", 
         width = 13,
         height = 8, 
         units = "in", 
         dpi = "retina")
  print(p4b)
  # cleanup 
  sink(tempfile())
  rm(p0, p1, p2, p3, p4a, p4b, plot_table); gc(full = TRUE)
  sink()
})

rm(obj_list)

Multi-subject

Lastly, we bring the multi-subject simulated datasets into memory.

# 100 cells
tar_load(endo_sim_DEG_10_CELLS_100_balanced)
tar_load(endo_sim_DEG_20_CELLS_100_balanced)
tar_load(endo_sim_DEG_10_CELLS_100_unbalanced)
tar_load(endo_sim_DEG_20_CELLS_100_unbalanced)
# 500 cells
tar_load(endo_sim_DEG_10_CELLS_500_balanced)
tar_load(endo_sim_DEG_20_CELLS_500_balanced)
tar_load(endo_sim_DEG_10_CELLS_500_unbalanced)
tar_load(endo_sim_DEG_20_CELLS_500_unbalanced)
# 1,000 cells 
tar_load(endo_sim_DEG_10_CELLS_1000_balanced)
tar_load(endo_sim_DEG_20_CELLS_1000_balanced)
tar_load(endo_sim_DEG_10_CELLS_1000_unbalanced)
tar_load(endo_sim_DEG_20_CELLS_1000_unbalanced)
# 2,500 cells
tar_load(endo_sim_DEG_10_CELLS_2500_balanced)
tar_load(endo_sim_DEG_20_CELLS_2500_balanced)
tar_load(endo_sim_DEG_10_CELLS_2500_unbalanced)
tar_load(endo_sim_DEG_20_CELLS_2500_unbalanced)
# 5,000 cells 
tar_load(endo_sim_DEG_10_CELLS_5000_balanced)
tar_load(endo_sim_DEG_20_CELLS_5000_balanced)
tar_load(endo_sim_DEG_10_CELLS_5000_unbalanced)
tar_load(endo_sim_DEG_20_CELLS_5000_unbalanced)
# coerce to list & process
obj_list <- purrr::map(ls(pattern = "endo_sim*balanced"), function(sim) {
  obj <- eval(as.symbol(sim))
  obj <- as.Seurat(obj, counts = "counts", data = "logcounts")
  obj@meta.data <- mutate(obj@meta.data, 
                          perc_deg = paste0(as.numeric(stringr::str_remove(stringr::str_extract(sim, "endo_sim_DEG_.."), "endo_sim_DEG_")), "%"), 
                          n_cells = as.character(ncol(obj)), 
                          n_genes = as.character(nrow(obj)), 
                          allocation = ifelse(grepl("_balanced", sim), "Balanced", "Unbalanced"), 
                          sce_name = sim)
  return(obj)
})
rm(list = ls(pattern = "endo_sim")); gc(full = TRUE)

We generate the final set of plots.

purrr::walk(obj_list, function(z) {
  # gather metadata 
  obj_name <- z@meta.data$sce_name[1]
  n_cells <- z@meta.data$n_cells[1]
  n_genes <- z@meta.data$n_genes[1]
  perc_deg <- z@meta.data$perc_deg[1]
  allocation <- z@meta.data$allocation[1]
  n_subjects <- length(unique(z@meta.data$subject))
  # summary stat table 
  sparsity_count <- mean(z@assays$originalexp@counts == 0)
  mean_count <- mean(z@assays$originalexp@counts)
  med_count <- ifelse(sparsity_count > 0.5, 0, median(z@assays$originalexp@counts))
  sd_count <- sd(z@assays$originalexp@counts)
  var_count <- sd_count^2 
  range_count <- range(z@assays$originalexp@counts)
  summary_df <- data.frame(metric = c("Mean", "Median", "S.D.", "Variance", "Range", "Sparsity"), 
                           value = c(round(mean_count, 2), 
                                     round(med_count, 2), 
                                     round(sd_count, 2), 
                                     round(var_count, 2),
                                     paste0("(", range_count[1], ", ", range_count[2], ")"), 
                                     paste0(round(sparsity_count, 4) * 100, "%")))
  # create counts histogram
  p0 <- data.frame(x = as.numeric(z@assays$originalexp@counts)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "dodgerblue") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        scale_x_continuous(labels = scales::label_comma()) + 
        labs(x = "Raw Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create log counts histogram
  p1 <- data.frame(x = as.numeric(z@assays$originalexp@data)) %>% 
        ggplot(aes(x = x)) + 
        geom_histogram(fill = "forestgreen") + 
        scale_y_continuous(labels = scales::label_scientific()) + 
        labs(x = "Normalized Expression", y = "Frequency") + 
        theme_classic(base_size = 14)
  # create UMAP by cluster & subject
  legend_clust <- ggpubr::get_legend(p = (data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                                                     UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                                                     cluster = z$label) %>% 
                                          ggplot(aes(x = UMAP1, y = UMAP2, color = cluster)) + 
                                          geom_point() + 
                                          scale_color_manual(values = paletteer_d("ggsci::category20_d3")[1:length(unique(z$label))]) + 
                                          labs(color = "Louvain\nCluster") + 
                                          theme_classic(base_size = 14) + theme(legend.text.align = 0.5)))
                                          
  legend_subj <- ggpubr::get_legend(p = (data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                                                    UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                                                    subject = z$subject) %>% 
                                         ggplot(aes(x = UMAP1, y = UMAP2, color = subject)) + 
                                         geom_point() + 
                                         scale_color_manual(values = paletteer_d("ggsci::category20_d3")[(length(unique(z$label)) + 1):20]) + 
                                         labs(color = "Subject") + 
                                         theme_classic(base_size = 14)))
  p2a <- data.frame(UMAP1 = z@reductions$UMAP@cell.embeddings[, 1], 
                    UMAP2 = z@reductions$UMAP@cell.embeddings[, 2], 
                    cluster = z$label, 
                    subject = z$subject) %>% 
           tidyr::pivot_longer(cols = c(cluster, subject), names_to = "ident", values_to = "ident_value") %>% 
           mutate(ident = case_when(ident == "cluster" ~ "Louvain Cluster", 
                                    TRUE ~ "Subject")) %>% 
           ggplot(aes(x = UMAP1, y = UMAP2, color = ident_value, group = ident)) + 
           facet_wrap(~ident) + 
           geom_point() + 
           scale_color_manual(values = paletteer_d("ggsci::category20_d3")) + 
           labs(x = "UMAP 1", y = "UMAP 2") + 
           theme_classic(base_size = 14) + 
           theme(legend.position = "none", 
                 axis.text = element_blank(), 
                 axis.ticks = element_blank())
  p2b <- (p2a + (wrap_elements(legend_clust) / wrap_elements(legend_subj)) + 
         plot_layout(ncol = 2, widths = c(4, 1)))
  # create PCA of cell ordering 
  p3a <- data.frame(PC1 = z@reductions$PCA@cell.embeddings[, 1], 
                    PC2 = z@reductions$PCA@cell.embeddings[, 2], 
                    cell_time = z$cell_time_normed, 
                    subject = z$subject) %>% 
         ggplot(aes(x = PC1, y = PC2, color = cell_time)) + 
         facet_wrap(~subject) + 
         geom_point() + 
         scale_color_gradientn(colors = paletteer_d("wesanderson::Zissou1")) + 
         labs(x = "PC 1", y = "PC 2", color = "True Ordering") + 
         theme_classic(base_size = 14) + 
         theme(axis.ticks = element_blank(), 
               axis.text = element_blank())
  p3b <- data.frame(PC1 = z@reductions$PCA@cell.embeddings[, 1], 
                    PC2 = z@reductions$PCA@cell.embeddings[, 2], 
                    cell_time = z$cell_time_normed) %>% 
       ggplot(aes(x = PC1, y = PC2, color = cell_time)) + 
       geom_point() + 
       scale_color_gradientn(colors = paletteer_d("wesanderson::Zissou1")) + 
       labs(x = "PC 1", y = "PC 2", color = "True Ordering") + 
       theme_classic(base_size = 14) + 
       theme(axis.ticks = element_blank(), 
             axis.text = element_blank())
  p3c <- (p3a | p3b) + 
         plot_layout(guides = "collect", widths = c(3, 2), ncol = 2)
  # table of simulation parameters 
  param_df <- data.frame(metric = c("N Cells", "N Genes", "% Dynamic", "Allocation", "N Subjects"), 
                         value = c(as.character(n_cells), 
                                   as.character(n_genes),
                                   perc_deg, 
                                   allocation, 
                                   n_subjects))
  plot_table <- rbind(summary_df, param_df) %>% 
                gridExtra::tableGrob(rows = NULL, 
                                     cols = c("Metric", "Value"), 
                                     theme = gridExtra::ttheme_minimal(core = list(fg_params = list(hjust = 0, x = 0.05)), 
                                                                       colhead = list(fg_params = list(hjust = 0, x = 0.05)))) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 2, 
                                        b = nrow(.), 
                                        l = 1, 
                                        r = ncol(.)) %>% 
                gtable::gtable_add_grob(grobs = grid::rectGrob(gp = grid::gpar(fill = NA, lwd = 2)), 
                                        t = 1, 
                                        l = 1, 
                                        r = ncol(.))
  # align everything 
  p4a <- ((p0 | p1 | plot_table) + plot_layout(widths = c(1, 1, 0.5))) / (p2b | p3c) + 
         plot_layout(nrow = 2) + 
         plot_annotation(title = paste0("Metrics for dataset: ", obj_name))
  # save & print plot
  ggsave(filename = paste0("QC_", obj_name, ".pdf"),
         plot = p4b, 
         device = "pdf", 
         path = "/blue/rbacher/j.leary/repos/scLANE_Analysis/Figures/QC_Plots/", 
         width = 13,
         height = 9, 
         units = "in", 
         dpi = "retina")
  print(p4a)
  # cleanup 
  sink(tempfile())
  rm(p0, p1, p2a, p2b, p3a, p3b, p3c, p4a, plot_table); gc(full = TRUE)
  sink()
})
rm(obj_list)

Session Info

sessioninfo::session_info()
## ─ Session info ───────────────────────────────────────────────────────────────
##  setting  value
##  version  R version 4.2.2 Patched (2022-11-10 r83330)
##  os       Ubuntu 22.04.1 LTS
##  system   x86_64, linux-gnu
##  ui       X11
##  language (EN)
##  collate  en_US.UTF-8
##  ctype    en_US.UTF-8
##  tz       America/New_York
##  date     2023-01-14
##  pandoc   2.9.2.1 @ /usr/bin/ (via rmarkdown)
## 
## ─ Packages ───────────────────────────────────────────────────────────────────
##  package                * version   date (UTC) lib source
##  abind                    1.4-5     2016-07-21 [2] CRAN (R 4.2.0)
##  AnnotationDbi            1.58.0    2022-04-26 [2] Bioconductor
##  AnnotationFilter         1.20.0    2022-04-26 [2] Bioconductor
##  AnnotationHub            3.4.0     2022-04-26 [2] Bioconductor
##  assertthat               0.2.1     2019-03-21 [2] CRAN (R 4.2.0)
##  backports                1.4.1     2021-12-13 [2] CRAN (R 4.2.0)
##  base64url                1.4       2018-05-14 [2] CRAN (R 4.2.0)
##  basilisk                 1.8.1     2022-08-25 [2] Bioconductor
##  basilisk.utils           1.8.0     2022-04-26 [2] Bioconductor
##  beachmat                 2.12.0    2022-04-26 [2] Bioconductor
##  beeswarm                 0.4.0     2021-06-01 [2] CRAN (R 4.2.0)
##  bigassertr               0.1.5     2021-07-08 [2] CRAN (R 4.2.0)
##  bigparallelr             0.3.2     2021-10-02 [2] CRAN (R 4.2.0)
##  bigstatsr                1.5.6     2022-02-03 [2] CRAN (R 4.2.0)
##  Biobase                * 2.56.0    2022-04-26 [2] Bioconductor
##  BiocFileCache            2.4.0     2022-04-26 [2] Bioconductor
##  BiocGenerics           * 0.42.0    2022-04-26 [2] Bioconductor
##  BiocIO                   1.6.0     2022-04-26 [2] Bioconductor
##  BiocManager              1.30.18   2022-05-18 [1] CRAN (R 4.2.0)
##  BiocNeighbors            1.14.0    2022-04-26 [2] Bioconductor
##  BiocParallel             1.30.3    2022-06-05 [2] Bioconductor
##  BiocSingular             1.12.0    2022-04-26 [2] Bioconductor
##  BiocVersion              3.15.2    2022-03-29 [2] Bioconductor
##  biomaRt                  2.52.0    2022-04-26 [2] Bioconductor
##  Biostrings               2.64.1    2022-08-18 [2] Bioconductor
##  bit                      4.0.4     2020-08-04 [2] CRAN (R 4.2.0)
##  bit64                    4.0.5     2020-08-30 [2] CRAN (R 4.2.0)
##  bitops                   1.0-7     2021-04-24 [2] CRAN (R 4.2.0)
##  blob                     1.2.3     2022-04-10 [2] CRAN (R 4.2.0)
##  bluster                  1.6.0     2022-04-26 [2] Bioconductor
##  broom                    1.0.1     2022-08-29 [2] CRAN (R 4.2.1)
##  bslib                    0.4.0     2022-07-16 [2] CRAN (R 4.2.0)
##  cachem                   1.0.6     2021-08-19 [2] CRAN (R 4.2.0)
##  callr                    3.7.2     2022-08-22 [2] CRAN (R 4.2.1)
##  cellranger               1.1.0     2016-07-27 [2] CRAN (R 4.2.0)
##  cli                      3.4.1     2022-09-23 [2] CRAN (R 4.2.1)
##  cluster                  2.1.4     2022-08-22 [4] CRAN (R 4.2.1)
##  codetools                0.2-18    2020-11-04 [4] CRAN (R 4.2.0)
##  colorspace               2.0-3     2022-02-21 [2] CRAN (R 4.2.0)
##  cowplot                  1.1.1     2020-12-30 [2] CRAN (R 4.2.0)
##  crayon                   1.5.2     2022-09-29 [2] CRAN (R 4.2.1)
##  curl                     4.3.2     2021-06-23 [2] CRAN (R 4.2.0)
##  data.table               1.14.2    2021-09-27 [2] CRAN (R 4.2.0)
##  DBI                      1.1.3     2022-06-18 [2] CRAN (R 4.2.0)
##  dbplyr                   2.2.1     2022-06-27 [2] CRAN (R 4.2.0)
##  DelayedArray             0.22.0    2022-04-26 [2] Bioconductor
##  DelayedMatrixStats       1.18.1    2022-09-27 [2] Bioconductor
##  deldir                   1.0-6     2021-10-23 [2] CRAN (R 4.2.0)
##  digest                   0.6.30    2022-10-18 [2] CRAN (R 4.2.1)
##  dir.expiry               1.4.0     2022-04-26 [2] Bioconductor
##  doParallel               1.0.17    2022-02-07 [2] CRAN (R 4.2.0)
##  dplyr                  * 1.0.9     2022-04-28 [1] CRAN (R 4.2.0)
##  dqrng                    0.3.0     2021-05-01 [2] CRAN (R 4.2.0)
##  edgeR                    3.38.4    2022-08-07 [2] Bioconductor
##  ellipsis                 0.3.2     2021-04-29 [2] CRAN (R 4.2.0)
##  ensembldb                2.20.2    2022-06-16 [2] Bioconductor
##  evaluate                 0.16      2022-08-09 [2] CRAN (R 4.2.0)
##  ExperimentHub            2.4.0     2022-04-26 [2] Bioconductor
##  fansi                    1.0.3     2022-03-24 [2] CRAN (R 4.2.0)
##  farver                   2.1.1     2022-07-06 [2] CRAN (R 4.2.0)
##  fastmap                  1.1.0     2021-01-25 [2] CRAN (R 4.2.0)
##  filelock                 1.0.2     2018-10-05 [2] CRAN (R 4.2.0)
##  fitdistrplus             1.1-8     2022-03-10 [2] CRAN (R 4.2.0)
##  flock                    0.7       2016-11-12 [2] CRAN (R 4.2.0)
##  FNN                      1.1.3.1   2022-05-23 [2] CRAN (R 4.2.0)
##  forcats                * 0.5.2     2022-08-19 [2] CRAN (R 4.2.1)
##  foreach                  1.5.2     2022-02-02 [1] CRAN (R 4.2.0)
##  fs                       1.5.2     2021-12-08 [2] CRAN (R 4.2.0)
##  future                 * 1.28.0    2022-09-02 [2] CRAN (R 4.2.1)
##  future.apply             1.9.1     2022-09-07 [2] CRAN (R 4.2.1)
##  future.callr           * 0.8.0     2022-04-01 [1] CRAN (R 4.2.0)
##  gargle                   1.2.1     2022-09-08 [2] CRAN (R 4.2.1)
##  generics                 0.1.3     2022-07-05 [1] CRAN (R 4.2.0)
##  GenomeInfoDb           * 1.32.4    2022-09-06 [2] Bioconductor
##  GenomeInfoDbData         1.2.8     2022-06-16 [2] Bioconductor
##  GenomicAlignments        1.32.1    2022-07-24 [2] Bioconductor
##  GenomicFeatures          1.48.4    2022-09-20 [2] Bioconductor
##  GenomicRanges          * 1.48.0    2022-04-26 [2] Bioconductor
##  ggbeeswarm               0.6.0     2017-08-07 [2] CRAN (R 4.2.0)
##  ggplot2                * 3.3.6     2022-05-03 [2] CRAN (R 4.2.0)
##  ggrepel                  0.9.1     2021-01-15 [1] CRAN (R 4.1.1)
##  ggridges                 0.5.4     2022-09-26 [2] CRAN (R 4.2.1)
##  globals                  0.16.1    2022-08-28 [2] CRAN (R 4.2.1)
##  glue                     1.6.2     2022-02-24 [2] CRAN (R 4.2.0)
##  goftest                  1.2-3     2021-10-07 [2] CRAN (R 4.2.0)
##  googledrive              2.0.0     2021-07-08 [2] CRAN (R 4.2.0)
##  googlesheets4            1.0.1     2022-08-13 [2] CRAN (R 4.2.0)
##  gridExtra                2.3       2017-09-09 [2] CRAN (R 4.2.0)
##  gtable                   0.3.1     2022-09-01 [2] CRAN (R 4.2.1)
##  haven                    2.5.1     2022-08-22 [2] CRAN (R 4.2.1)
##  here                     1.0.1     2020-12-13 [2] CRAN (R 4.2.0)
##  highr                    0.9       2021-04-16 [2] CRAN (R 4.2.0)
##  hms                      1.1.2     2022-08-19 [2] CRAN (R 4.2.1)
##  htmltools                0.5.3     2022-07-18 [2] CRAN (R 4.2.0)
##  htmlwidgets              1.5.4     2021-09-08 [2] CRAN (R 4.2.0)
##  httpuv                   1.6.6     2022-09-08 [2] CRAN (R 4.2.1)
##  httr                     1.4.4     2022-08-17 [2] CRAN (R 4.2.1)
##  ica                      1.0-3     2022-07-08 [2] CRAN (R 4.2.0)
##  igraph                 * 1.3.5     2022-09-22 [2] CRAN (R 4.2.1)
##  interactiveDisplayBase   1.34.0    2022-04-26 [2] Bioconductor
##  iotools                  0.3-2     2021-07-23 [2] CRAN (R 4.2.0)
##  IRanges                * 2.30.1    2022-08-18 [2] Bioconductor
##  irlba                    2.3.5.1   2022-10-03 [2] CRAN (R 4.2.1)
##  iterators                1.0.14    2022-02-05 [2] CRAN (R 4.2.0)
##  jquerylib                0.1.4     2021-04-26 [2] CRAN (R 4.2.0)
##  jsonlite                 1.8.2     2022-10-02 [2] CRAN (R 4.2.1)
##  KEGGREST                 1.36.3    2022-07-12 [2] Bioconductor
##  KernSmooth               2.23-20   2021-05-03 [4] CRAN (R 4.2.0)
##  knitr                    1.41      2022-11-18 [2] CRAN (R 4.2.2)
##  labeling                 0.4.2     2020-10-20 [2] CRAN (R 4.2.0)
##  later                    1.3.0     2021-08-18 [2] CRAN (R 4.2.0)
##  lattice                  0.20-45   2021-09-22 [4] CRAN (R 4.2.0)
##  lazyeval                 0.2.2     2019-03-15 [2] CRAN (R 4.2.0)
##  leiden                   0.4.3     2022-09-10 [2] CRAN (R 4.2.1)
##  lifecycle                1.0.3     2022-10-07 [2] CRAN (R 4.2.1)
##  limma                    3.52.4    2022-09-27 [2] Bioconductor
##  listenv                  0.8.0     2019-12-05 [2] CRAN (R 4.2.0)
##  lmtest                   0.9-40    2022-03-21 [2] CRAN (R 4.2.0)
##  locfit                   1.5-9.6   2022-07-11 [2] CRAN (R 4.2.0)
##  logging                  0.10-108  2019-07-14 [1] CRAN (R 4.1.1)
##  lubridate                1.8.0     2021-10-07 [2] CRAN (R 4.2.0)
##  magrittr                 2.0.3     2022-03-30 [1] CRAN (R 4.2.0)
##  MASS                     7.3-58.1  2022-08-03 [4] CRAN (R 4.2.1)
##  Matrix                   1.5-1     2022-09-13 [2] CRAN (R 4.2.1)
##  MatrixGenerics         * 1.8.1     2022-06-26 [2] Bioconductor
##  matrixStats            * 0.62.0    2022-04-19 [2] CRAN (R 4.2.0)
##  memoise                  2.0.1     2021-11-26 [2] CRAN (R 4.2.0)
##  metapod                  1.4.0     2022-04-26 [2] Bioconductor
##  mgcv                     1.8-41    2022-10-21 [4] CRAN (R 4.2.1)
##  mime                     0.12      2021-09-28 [2] CRAN (R 4.2.0)
##  miniUI                   0.1.1.1   2018-05-18 [2] CRAN (R 4.2.0)
##  modelr                   0.1.9     2022-08-19 [2] CRAN (R 4.2.1)
##  munsell                  0.5.0     2018-06-12 [2] CRAN (R 4.2.0)
##  nlme                     3.1-160   2022-10-10 [4] CRAN (R 4.2.1)
##  paletteer              * 1.5.0     2022-10-19 [1] CRAN (R 4.2.1)
##  parallelly               1.32.1    2022-07-21 [2] CRAN (R 4.2.0)
##  patchwork              * 1.1.2     2022-08-19 [1] CRAN (R 4.2.0)
##  pbapply                  1.5-0     2021-09-16 [2] CRAN (R 4.2.0)
##  pillar                   1.8.1     2022-08-19 [2] CRAN (R 4.2.1)
##  pkgconfig                2.0.3     2019-09-22 [2] CRAN (R 4.2.0)
##  plotly                   4.10.0    2021-10-09 [2] CRAN (R 4.2.0)
##  plyr                     1.8.7     2022-03-24 [2] CRAN (R 4.2.0)
##  png                      0.1-7     2013-12-03 [2] CRAN (R 4.2.0)
##  polyclip                 1.10-0    2019-03-14 [2] CRAN (R 4.2.0)
##  prettyunits              1.1.1     2020-01-24 [2] CRAN (R 4.2.0)
##  prismatic                1.1.1     2022-08-15 [1] CRAN (R 4.2.0)
##  processx                 3.7.0     2022-07-07 [2] CRAN (R 4.2.0)
##  progress                 1.2.2     2019-05-16 [2] CRAN (R 4.2.0)
##  progressr                0.11.0    2022-09-02 [2] CRAN (R 4.2.1)
##  promises                 1.2.0.1   2021-02-11 [2] CRAN (R 4.2.0)
##  ProtGenerics             1.28.0    2022-04-26 [2] Bioconductor
##  ps                       1.7.1     2022-06-18 [2] CRAN (R 4.2.0)
##  purrr                  * 0.3.5     2022-10-06 [2] CRAN (R 4.2.1)
##  R6                       2.5.1     2021-08-19 [2] CRAN (R 4.2.0)
##  ragg                     1.2.3     2022-09-29 [2] CRAN (R 4.2.1)
##  RANN                     2.6.1     2019-01-08 [2] CRAN (R 4.2.0)
##  rappdirs                 0.3.3     2021-01-31 [2] CRAN (R 4.2.0)
##  RColorBrewer             1.1-3     2022-04-03 [2] CRAN (R 4.2.0)
##  Rcpp                     1.0.9     2022-07-08 [2] CRAN (R 4.2.1)
##  RcppAnnoy                0.0.19    2021-07-30 [2] CRAN (R 4.2.0)
##  RcppZiggurat             0.1.6     2020-10-20 [2] CRAN (R 4.2.0)
##  RCurl                    1.98-1.9  2022-10-03 [2] CRAN (R 4.2.1)
##  readr                  * 2.1.3     2022-10-01 [2] CRAN (R 4.2.1)
##  readxl                   1.4.1     2022-08-17 [2] CRAN (R 4.2.1)
##  rematch2                 2.1.2     2020-05-01 [2] CRAN (R 4.2.0)
##  reprex                   2.0.2     2022-08-17 [2] CRAN (R 4.2.1)
##  reshape2                 1.4.4     2020-04-09 [2] CRAN (R 4.2.0)
##  restfulr                 0.0.15    2022-06-16 [2] CRAN (R 4.2.0)
##  reticulate               1.25      2022-05-11 [1] CRAN (R 4.2.0)
##  Rfast                    2.0.6     2022-02-16 [2] CRAN (R 4.2.0)
##  rgeos                    0.5-9     2021-12-15 [2] CRAN (R 4.2.0)
##  rjson                    0.2.21    2022-01-09 [2] CRAN (R 4.2.0)
##  rlang                    1.0.6     2022-09-24 [2] CRAN (R 4.2.1)
##  rmarkdown                2.16      2022-08-24 [1] CRAN (R 4.2.0)
##  ROCR                     1.0-11    2020-05-02 [2] CRAN (R 4.2.0)
##  rpart                    4.1.19    2022-10-21 [4] CRAN (R 4.2.1)
##  rprojroot                2.0.3     2022-04-02 [2] CRAN (R 4.2.0)
##  Rsamtools                2.12.0    2022-04-26 [2] Bioconductor
##  RSQLite                  2.2.17    2022-09-10 [2] CRAN (R 4.2.1)
##  rsvd                     1.0.5     2021-04-16 [2] CRAN (R 4.2.0)
##  rtracklayer              1.56.1    2022-06-23 [2] Bioconductor
##  Rtsne                    0.16      2022-04-17 [2] CRAN (R 4.2.0)
##  rvest                    1.0.3     2022-08-19 [2] CRAN (R 4.2.1)
##  S4Vectors              * 0.34.0    2022-04-26 [2] Bioconductor
##  sass                     0.4.2     2022-07-16 [1] CRAN (R 4.2.0)
##  scaffold               * 0.2.0     2022-09-04 [1] Github (rhondabacher/scaffold@714c319)
##  ScaledMatrix             1.4.1     2022-09-11 [2] Bioconductor
##  scales                   1.2.1     2022-08-20 [2] CRAN (R 4.2.1)
##  scater                 * 1.24.0    2022-04-26 [2] Bioconductor
##  scattermore              0.8       2022-02-14 [2] CRAN (R 4.2.0)
##  scran                  * 1.24.1    2022-09-11 [2] Bioconductor
##  scRNAseq               * 2.10.0    2022-04-28 [1] Bioconductor
##  sctransform              0.3.4     2022-08-20 [1] CRAN (R 4.2.0)
##  scuttle                * 1.6.3     2022-08-23 [2] Bioconductor
##  sessioninfo              1.2.2     2021-12-06 [2] CRAN (R 4.2.0)
##  Seurat                 * 4.1.1     2022-05-02 [1] CRAN (R 4.2.0)
##  SeuratObject           * 4.1.2     2022-09-20 [2] CRAN (R 4.2.1)
##  shiny                    1.7.2     2022-07-19 [2] CRAN (R 4.2.0)
##  SingleCellExperiment   * 1.18.0    2022-04-26 [2] Bioconductor
##  sp                     * 1.5-0     2022-06-05 [2] CRAN (R 4.2.0)
##  sparseMatrixStats        1.8.0     2022-04-26 [2] Bioconductor
##  spatstat.core            2.4-4     2022-05-18 [2] CRAN (R 4.2.0)
##  spatstat.data            2.2-0     2022-04-18 [2] CRAN (R 4.2.0)
##  spatstat.geom            2.4-0     2022-03-29 [2] CRAN (R 4.2.0)
##  spatstat.random          2.2-0     2022-03-30 [2] CRAN (R 4.2.0)
##  spatstat.sparse          2.1-1     2022-04-18 [2] CRAN (R 4.2.0)
##  spatstat.utils           2.3-1     2022-05-06 [2] CRAN (R 4.2.0)
##  statmod                  1.4.37    2022-08-12 [2] CRAN (R 4.2.0)
##  stringi                  1.7.8     2022-07-11 [2] CRAN (R 4.2.0)
##  stringr                * 1.4.1     2022-08-20 [2] CRAN (R 4.2.1)
##  SummarizedExperiment   * 1.26.1    2022-04-29 [2] Bioconductor
##  survival                 3.4-0     2022-08-09 [4] CRAN (R 4.2.1)
##  systemfonts              1.0.4     2022-02-11 [2] CRAN (R 4.2.0)
##  tarchetypes            * 0.7.0     2022-08-05 [1] CRAN (R 4.2.1)
##  targets                * 0.13.1    2022-08-05 [1] CRAN (R 4.2.0)
##  tensor                   1.5       2012-05-05 [2] CRAN (R 4.2.0)
##  textshaping              0.3.6     2021-10-13 [2] CRAN (R 4.2.0)
##  tibble                 * 3.1.8     2022-07-22 [2] CRAN (R 4.2.0)
##  tidyr                  * 1.2.1     2022-09-08 [2] CRAN (R 4.2.1)
##  tidyselect               1.2.0     2022-10-10 [2] CRAN (R 4.2.1)
##  tidyverse              * 1.3.2     2022-07-18 [1] CRAN (R 4.2.0)
##  tzdb                     0.3.0     2022-03-28 [2] CRAN (R 4.2.0)
##  utf8                     1.2.2     2021-07-24 [2] CRAN (R 4.2.0)
##  uwot                     0.1.14    2022-08-22 [2] CRAN (R 4.2.1)
##  vctrs                    0.5.0     2022-10-22 [2] CRAN (R 4.2.1)
##  vipor                    0.4.5     2017-03-22 [2] CRAN (R 4.2.0)
##  viridis                  0.6.2     2021-10-13 [2] CRAN (R 4.2.0)
##  viridisLite              0.4.1     2022-08-22 [2] CRAN (R 4.2.1)
##  withr                    2.5.0     2022-03-03 [2] CRAN (R 4.2.0)
##  wrswoR                   1.1.1     2020-07-26 [1] CRAN (R 4.1.1)
##  xfun                     0.35      2022-11-16 [2] CRAN (R 4.2.2)
##  XML                      3.99-0.11 2022-10-03 [2] CRAN (R 4.2.1)
##  xml2                     1.3.3     2021-11-30 [2] CRAN (R 4.2.0)
##  xtable                   1.8-4     2019-04-21 [2] CRAN (R 4.2.0)
##  XVector                  0.36.0    2022-04-26 [2] Bioconductor
##  yaml                     2.3.5     2022-02-21 [2] CRAN (R 4.2.0)
##  zellkonverter            1.6.5     2022-09-13 [1] Bioconductor
##  zlibbioc                 1.42.0    2022-04-26 [2] Bioconductor
##  zoo                      1.8-11    2022-09-17 [2] CRAN (R 4.2.1)
## 
##  [1] /home/j.leary/r_packages_default
##  [2] /usr/local/lib/R/site-library
##  [3] /usr/lib/R/site-library
##  [4] /usr/lib/R/library
## 
## ─ Python configuration ───────────────────────────────────────────────────────
##  python:         /home/j.leary/.conda/envs/HPG_venv/bin/python
##  libpython:      /home/j.leary/.conda/envs/HPG_venv/lib/libpython3.10.so
##  pythonhome:     /home/j.leary/.conda/envs/HPG_venv:/home/j.leary/.conda/envs/HPG_venv
##  version:        3.10.8 | packaged by conda-forge | (main, Nov 22 2022, 08:26:04) [GCC 10.4.0]
##  numpy:          /home/j.leary/.conda/envs/HPG_venv/lib/python3.10/site-packages/numpy
##  numpy_version:  1.23.5
##  scvelo:         /home/j.leary/.conda/envs/HPG_venv/lib/python3.10/site-packages/scvelo
##  
##  NOTE: Python version was forced by use_python function
## 
## ──────────────────────────────────────────────────────────────────────────────
LS0tCnRpdGxlOiAiYHNjTEFORWAgU2ltdWxhdGlvbiBTdHVkeSAtIFNpbXVsYXRlZCBEYXRhIFF1YWxpdHkgQ29udHJvbCIKc3VidGl0bGU6ICJVRiBEZXB0LiBvZiBCaW9zdGF0aXN0aWNzIC0gQmFjaGVyIEdyb3VwIgphdXRob3I6ICJKYWNrIExlYXJ5IiAKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBqb3VybmFsCiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUgCiAgICB0b2M6IGZhbHNlCiAgICB0b2NfZmxvYXQ6IGZhbHNlCiAgICBkZl9wcmludDoga2FibGUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSk7IHNldC5zZWVkKDMxMikgICMgbHVja3kgc2VlZApgYGAKCiMgTGlicmFyaWVzIAoKRmlyc3Qgd2UnbGwgbG9hZCBpbiB0aGUgcGFja2FnZXMgd2UgbmVlZCB0byB0aWR5ICYgYW5hbHl6ZSBvdXIgc2ltdWxhdGlvbiByZXN1bHRzLiAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KbGlicmFyeShkcGx5cikgICAgICAgICAgICAgICAgICMgZGF0YSBtYW5pcHVsYXRpb24gCmxpYnJhcnkoc2NyYW4pICAgICAgICAgICAgICAgICAjIHNjUk5BIHRvb2xzIApsaWJyYXJ5KHNjYXRlcikgICAgICAgICAgICAgICAgIyBtb3JlIHNjUk5BIHRvb2xzCmxpYnJhcnkoU2V1cmF0KSAgICAgICAgICAgICAgICAjIHNjUk5BIG1ldGhvZHMgJiBkYXRhIHN0cnVjdHVyZXMKbGlicmFyeShnZ3Bsb3QyKSAgICAgICAgICAgICAgICMgcGxvdHMKbGlicmFyeSh0YXJnZXRzKSAgICAgICAgICAgICAgICMgcGlwZWxpbmUgdG9vbHMKbGlicmFyeShwYWxldHRlZXIpICAgICAgICAgICAgICMgcGxvdCBjb2xvcnMgCmxpYnJhcnkocGF0Y2h3b3JrKSAgICAgICAgICAgICAjIHBsb3QgY29tYmluYXRpb24KbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkgICMgc2NSTkEgZGF0YSBzdHJ1Y3R1cmVzIApgYGAKCiMgTHVuZyBSZWZlcmVuY2UgCgpGaXJzdCB3ZSdsbCBsb2FkIGluIHRoZSBsdW5nIHJlZmVyZW5jZSBkYXRhc2V0IGZyb20gdGhlIGB7c2NSTkFzZXF9YCBwYWNrYWdlICYgcHJvY2VzcyBpdCBsaWtlIHdlIHdvdWxkIG91ciBzaW11bGF0ZWQgZGF0YXNldHMuIFBsZWFzZSBpZ25vcmUgdGhlIGNhbGxzIHRvIGBnYygpYCBsaXR0ZXJlZCB0aHJvdWdob3V0IHRoZSBjb2RlLCBpdCB3YXMgc28gZGlmZmljdWx0IHRvIGdldCBhbGwgdGhlc2UgZGF0YXNldHMgaW50byBtZW1vcnkgJiBwcm9jZXNzZWQgd2l0aG91dCBteSBSIHNlc3Npb24gY3Jhc2hpbmcgb3ZlciAmIG92ZXIgYWdhaW4uIAoKYGBge3IsIHJlc3VsdHM9J2hpZGUnfQpsdW5nX2RhdGEgPC0gc2NSTkFzZXE6OlppbGlvbmlzTHVuZ0RhdGEod2hpY2ggPSAiaHVtYW4iLCBmaWx0ZXIgPSBUUlVFKQpsdW5nX2RhdGFfY2xlYW4gPC0gbHVuZ19kYXRhW3Jvd1N1bXMoY291bnRzKGx1bmdfZGF0YSkgPiAwKSA+PSAzLCBdICAjIGdlbmVzIGluIGF0IGxlYXN0IDMgY2VsbHMKY29sbmFtZXMobHVuZ19kYXRhX2NsZWFuKSA8LSBtYWtlLnVuaXF1ZShjb2xuYW1lcyhsdW5nX2RhdGFfY2xlYW4pKQpnYyhmdWxsID0gVFJVRSkKIyBwcm9jZXNzIApsdW5nX2RhdGFfY2xlYW4gPC0gbG9nTm9ybUNvdW50cyhsdW5nX2RhdGFfY2xlYW4pCnZhcl9kZWNvbXAgPC0gbW9kZWxHZW5lVmFyKGx1bmdfZGF0YV9jbGVhbikKdG9wMmtfaHZncyA8LSBnZXRUb3BIVkdzKHZhcl9kZWNvbXAsIG4gPSAyMDAwKQpnYyhmdWxsID0gVFJVRSkKbHVuZ19kYXRhX2NsZWFuIDwtIHJ1blBDQShsdW5nX2RhdGFfY2xlYW4sIHN1YnNldF9yb3cgPSB0b3Aya19odmdzKQpyZWR1Y2VkRGltKGx1bmdfZGF0YV9jbGVhbiwgIlBDQXN1YiIpIDwtIHJlZHVjZWREaW0obHVuZ19kYXRhX2NsZWFuLCAiUENBIilbLCAxOjMwLCBkcm9wID0gRkFMU0VdCmx1bmdfZGF0YV9jbGVhbiA8LSBydW5VTUFQKGx1bmdfZGF0YV9jbGVhbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpbXJlZCA9ICJQQ0FzdWIiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9kaW1yZWQgPSAxOjMwKQpnYyhmdWxsID0gVFJVRSkKZyA8LSBidWlsZFNOTkdyYXBoKGx1bmdfZGF0YV9jbGVhbiwgCiAgICAgICAgICAgICAgICAgICB1c2UuZGltcmVkID0gIlBDQXN1YiIsIAogICAgICAgICAgICAgICAgICAgayA9IDMwKQpjbHVzdGVycyA8LSBpZ3JhcGg6OmNsdXN0ZXJfbG91dmFpbihncmFwaCA9IGcpJG1lbWJlcnNoaXAKY29sTGFiZWxzKGx1bmdfZGF0YV9jbGVhbikgPC0gZmFjdG9yKGNsdXN0ZXJzKQpnYyhmdWxsID0gVFJVRSkKbHVuZ19kYXRhX2NsZWFuIDwtIGFzLlNldXJhdChsdW5nX2RhdGFfY2xlYW4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvdW50cyA9ICJjb3VudHMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gImxvZ2NvdW50cyIpCmdjKGZ1bGwgPSBUUlVFKQpgYGAKCk5leHQgd2UgY3JlYXRlIGEgdGFibGUgb2Ygc3VtbWFyeSBzdGF0aXN0aWNzICYgZGF0YXNldCBjaGFyYWN0ZXJpc3RpY3MuIFdlIHRoZW4gY3JlYXRlIGEgYHtnZ3Bsb3QyfWAtZnJpZW5kbHkgdmVyc2lvbiBvZiB0aGUgdGFibGUgdXNpbmcgYHtncmlkRXh0cmF9YCAmIGB7Z3RhYmxlfWAuIFRoaXMgYWxsb3dzIHVzIHRvIGluY2x1ZGUgdGhlIHRhYmxlIGFzIHBhcnQgb2YgYSBwbG90IG9iamVjdC4gCgpgYGB7ciwgcmVzdWx0cz0naGlkZSd9Cm5fY2VsbHMgPC0gbmNvbChsdW5nX2RhdGFfY2xlYW4pCm5fZ2VuZXMgPC0gbnJvdyhsdW5nX2RhdGFfY2xlYW4pCm1lYW5fY291bnQgPC0gbWVhbihsdW5nX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKbWVkX2NvdW50IDwtIDAKc2RfY291bnQgPC0gc2QobHVuZ19kYXRhX2NsZWFuQGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpCnZhcl9jb3VudCA8LSBzZF9jb3VudF4yICAjIGZhc3RlciAKcmFuZ2VfY291bnQgPC0gcmFuZ2UobHVuZ19kYXRhX2NsZWFuQGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpCnNwYXJzaXR5X2NvdW50IDwtIG1lYW4obHVuZ19kYXRhX2NsZWFuQGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMgPT0gMCkKc3VtbWFyeV9kZiA8LSBkYXRhLmZyYW1lKG1ldHJpYyA9IGMoIk1lYW4iLCAiTWVkaWFuIiwgIlMuRC4iLCAiVmFyaWFuY2UiLCAiUmFuZ2UiLCAiU3BhcnNpdHkiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGMocm91bmQobWVhbl9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZF9jb3VudCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoc2RfY291bnQsIDIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZCh2YXJfY291bnQsIDIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgiKCIsIHJhbmdlX2NvdW50WzFdLCAiLCAiLCByYW5nZV9jb3VudFsyXSwgIikiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKHJvdW5kKHNwYXJzaXR5X2NvdW50LCA0KSAqIDEwMCwgIiUiKSkpCnBsb3RfdGFibGUgPC0gZ3JpZEV4dHJhOjp0YWJsZUdyb2Ioc3VtbWFyeV9kZiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93cyA9IE5VTEwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJNZXRyaWMiLCAiVmFsdWUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUgPSBncmlkRXh0cmE6OnR0aGVtZV9taW5pbWFsKGNvcmUgPSBsaXN0KGZnX3BhcmFtcyA9IGxpc3QoaGp1c3QgPSAwLCB4ID0gMC4wNSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29saGVhZCA9IGxpc3QoZmdfcGFyYW1zID0gbGlzdChoanVzdCA9IDAsIHggPSAwLjA1KSkpKSAlPiUgCiAgICAgICAgICAgICAgZ3RhYmxlOjpndGFibGVfYWRkX2dyb2IoZ3JvYnMgPSBncmlkOjpyZWN0R3JvYihncCA9IGdyaWQ6OmdwYXIoZmlsbCA9IE5BLCBsd2QgPSAyKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHQgPSAyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiID0gbnJvdyguKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHIgPSBuY29sKC4pKSAlPiUgCiAgICAgICAgICAgICAgZ3RhYmxlOjpndGFibGVfYWRkX2dyb2IoZ3JvYnMgPSBncmlkOjpyZWN0R3JvYihncCA9IGdyaWQ6OmdwYXIoZmlsbCA9IE5BLCBsd2QgPSAyKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHQgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgciA9IG5jb2woLikpCmdjKGZ1bGwgPSBUUlVFKQpgYGAKClRoZSBmaXJzdCBwbG90IHdlIHdhbnQgaXMgYSBoaXN0b2dyYW0gb2YgcmF3IGNvdW50cy4gVW5mb3J0dW5hdGVseSwgdGhpcyBpcyBhcHBhcmVudGx5IHZlcnkgaGFyZCB0byBkbyBzaW5jZSBvdXIgZGF0YSBpcyBzbyBsYXJnZS4gV2UgZmlyc3QgY29udmVydCBvdXIgY291bnRzIG1hdHJpeCB0byBhIGZpbGUtYmFja2VkIG1hdHJpeCwgYW5kIG1hbnVhbGx5IGNvbXB1dGUgdGhlIGJpbiByYW5nZXMgd2UncmUgaW50ZXJlc3RlZCBpbiAod2l0aCAkYiA9IDIwJCBiaW5zKS4gTmV4dCwgd2UgaXRlcmF0ZSBvdmVyIHRoZSBiaW5zICYgc3VtIHRoZSBudW1iZXIgb2YgY291bnRzIHdpdGhpbiBlYWNoIHJhbmdlLiBIb3dldmVyLCB0aGlzIHRvbyBjYXVzZSBpc3N1ZXMsIHNvIHdlIGFsc28gaXRlcmF0ZSBvdmVyIHRoZSBjb2x1bW5zIG9mIHRoZSBtYXRyaXggKGNlbGxzKSwgYW5kIHN1bSB3aXRoaW4gZWFjaCBjZWxsIGZpcnN0LCB0aGVuIGFnZ3JlZ2F0ZSBhZnRlcndhcmRzIGZvciB0aGUgZmluYWwgcGVyLWJpbiB2YWx1ZS4gCgpgYGB7ciwgcmVzdWx0cz0naGlkZSd9CmJpbl9kZiA8LSBkYXRhLmZyYW1lKGxvd2VyX2JvdW5kID0gc2VxKHJhbmdlX2NvdW50WzFdLCByYW5nZV9jb3VudFsyXSwgbGVuZ3RoLm91dCA9IDIwKSkgJT4lIAogICAgICAgICAgbXV0YXRlKHVwcGVyX2JvdW5kID0gbGVhZChsb3dlcl9ib3VuZCksIAogICAgICAgICAgICAgICAgIGFjcm9zcyhjb250YWlucygiYm91bmQiKSwgXCh4KSByb3VuZCh4KSkpICU+JSAKICAgICAgICAgIGZpbHRlcighaXMubmEodXBwZXJfYm91bmQpKSAlPiUgCiAgICAgICAgICBtdXRhdGUobiA9IE5BX3JlYWxfKQpjb3VudHNfbWF0IDwtIGJpZ3N0YXRzcjo6YXNfRkJNKGFzLm1hdHJpeChsdW5nX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJpbnRlZ2VyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpc19yZWFkX29ubHkgPSBUUlVFKQpnYyhmdWxsID0gVFJVRSkKZm9yIChiIGluIHNlcShucm93KGJpbl9kZikpKSB7CiAgYmluX3N1bXNfdG1wIDwtIHZlY3RvcigibnVtZXJpYyIsIGxlbmd0aCA9IG5jb2woY291bnRzX21hdCkpCiAgZm9yIChqIGluIHNlcShuY29sKGNvdW50c19tYXQpKSkgewogICAgY29sX2ogPC0gY291bnRzX21hdFssIGpdCiAgICBpZiAoYiA9PSAxKSB7CiAgICAgIGJpbl9zdW1zX3RtcFtqXSA8LSBzdW0oY29sX2ogPj0gYmluX2RmJGxvd2VyX2JvdW5kW2JdICYgY29sX2ogPD0gYmluX2RmJHVwcGVyX2JvdW5kW2JdKQogICAgfSBlbHNlIHsKICAgICAgYmluX3N1bXNfdG1wW2pdIDwtIHN1bShjb2xfaiA+IGJpbl9kZiRsb3dlcl9ib3VuZFtiXSAmIGNvbF9qIDw9IGJpbl9kZiR1cHBlcl9ib3VuZFtiXSkKICAgIH0KICAgIHJtKGNvbF9qKQogIH0KICBiaW5fc3VtIDwtIHN1bShiaW5fc3Vtc190bXApCiAgYmluX2RmJG5bYl0gPC0gYmluX3N1bQogIGdjKGZ1bGwgPSBUUlVFKQp9CnJtKGNvdW50c19tYXQpOyBnYyhmdWxsID0gVFJVRSkKcDAgPC0gZ2dwbG90KGJpbl9kZiwgYWVzKHggPSBsb3dlcl9ib3VuZCwgeSA9IG4pKSArIAogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJkb2RnZXJibHVlIikgKyAKICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfc2NpZW50aWZpYygpKSArIAogICAgICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9jb21tYSgpKSArIAogICAgICBsYWJzKHggPSAiUmF3IEV4cHJlc3Npb24iLCB5ID0gIkZyZXF1ZW5jeSIpICsgCiAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpCmBgYAoKV2UnbGwgZG8gdGhlIHNhbWUgZm9yIHRoZSBub3JtYWxpemVkIGNvdW50cy4gCgpgYGB7ciwgcmVzdWx0cz0naGlkZSd9CmJpbl9kZiA8LSBkYXRhLmZyYW1lKGxvd2VyX2JvdW5kID0gc2VxKDAsIG1heChsdW5nX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGRhdGEpLCBsZW5ndGgub3V0ID0gMjApKSAlPiUgCiAgICAgICAgICBtdXRhdGUodXBwZXJfYm91bmQgPSBsZWFkKGxvd2VyX2JvdW5kKSkgJT4lIAogICAgICAgICAgZmlsdGVyKCFpcy5uYSh1cHBlcl9ib3VuZCkpICU+JSAKICAgICAgICAgIG11dGF0ZShuID0gTkFfcmVhbF8pCmNvdW50c19tYXQgPC0gYmlnc3RhdHNyOjphc19GQk0oYXMubWF0cml4KGx1bmdfZGF0YV9jbGVhbkBhc3NheXMkb3JpZ2luYWxleHBAZGF0YSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJmbG9hdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXNfcmVhZF9vbmx5ID0gVFJVRSkKZ2MoZnVsbCA9IFRSVUUpCmZvciAoYiBpbiBzZXEobnJvdyhiaW5fZGYpKSkgewogIGJpbl9zdW1zX3RtcCA8LSB2ZWN0b3IoIm51bWVyaWMiLCBsZW5ndGggPSBuY29sKGNvdW50c19tYXQpKQogIGZvciAoaiBpbiBzZXEobmNvbChjb3VudHNfbWF0KSkpIHsKICAgIGNvbF9qIDwtIGNvdW50c19tYXRbLCBqXQogICAgaWYgKGIgPT0gMSkgewogICAgICBiaW5fc3Vtc190bXBbal0gPC0gc3VtKGNvbF9qID49IGJpbl9kZiRsb3dlcl9ib3VuZFtiXSAmIGNvbF9qIDw9IGJpbl9kZiR1cHBlcl9ib3VuZFtiXSkKICAgIH0gZWxzZSB7CiAgICAgIGJpbl9zdW1zX3RtcFtqXSA8LSBzdW0oY29sX2ogPiBiaW5fZGYkbG93ZXJfYm91bmRbYl0gJiBjb2xfaiA8PSBiaW5fZGYkdXBwZXJfYm91bmRbYl0pCiAgICB9CiAgICBybShjb2xfaikKICB9CiAgYmluX3N1bSA8LSBzdW0oYmluX3N1bXNfdG1wKQogIGJpbl9kZiRuW2JdIDwtIGJpbl9zdW0KICBnYyhmdWxsID0gVFJVRSkKfQpybShjb3VudHNfbWF0KTsgZ2MoZnVsbCA9IFRSVUUpCnAxIDwtIGdncGxvdChiaW5fZGYsIGFlcyh4ID0gbG93ZXJfYm91bmQsIHkgPSBuKSkgKyAKICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAiZm9yZXN0Z3JlZW4iKSArIAogICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9zY2llbnRpZmljKCkpICsgCiAgICAgIGxhYnMoeCA9ICJOb3JtYWxpemVkIEV4cHJlc3Npb24iLCB5ID0gIkZyZXF1ZW5jeSIpICsgCiAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpCmBgYAoKTm93IHRoYXQgdGhlIHRyaWNreSBzdHVmZiBpcyBvdXQgb2YgdGhlIHdheSwgd2UgbWFrZSBhIFVNQVAgJiBQQ0EgcGxvdCBvZiB0aGUgdW5zdXBlcnZpc2VkIGNsdXN0ZXJpbmcuIAoKYGBge3J9CnAyIDwtIGRhdGEuZnJhbWUoVU1BUDEgPSBsdW5nX2RhdGFfY2xlYW5AcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICBVTUFQMiA9IGx1bmdfZGF0YV9jbGVhbkByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzWywgMl0sIAogICAgICAgICAgICAgICAgIGNsdXN0ZXIgPSBsdW5nX2RhdGFfY2xlYW4kbGFiZWwpICU+JSAKICAgICAgZ2dwbG90KGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjbHVzdGVyKSkgKyAKICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmNhdGVnb3J5MjBfZDMiKSkgKyAKICAgICAgbGFicyh4ID0gIlVNQVAgMSIsIHkgPSAiVU1BUCAyIiwgY29sb3IgPSAiTG91dmFpbiBDbHVzdGVyIikgKyAKICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICAgICAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIpKSkKcDMgPC0gZGF0YS5mcmFtZShQQzEgPSBsdW5nX2RhdGFfY2xlYW5AcmVkdWN0aW9ucyRQQ0FAY2VsbC5lbWJlZGRpbmdzWywgMV0sIAogICAgICAgICAgICAgICAgIFBDMiA9IGx1bmdfZGF0YV9jbGVhbkByZWR1Y3Rpb25zJFBDQUBjZWxsLmVtYmVkZGluZ3NbLCAyXSwgCiAgICAgICAgICAgICAgICAgY2x1c3RlciA9IGx1bmdfZGF0YV9jbGVhbiRsYWJlbCkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG9yID0gY2x1c3RlcikpICsgCiAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdnc2NpOjpjYXRlZ29yeTIwX2QzIikpICsgCiAgICAgIGxhYnMoeCA9ICJQQyAxIiwgeSA9ICJQQyAyIiwgY29sb3IgPSAiTG91dmFpbiBDbHVzdGVyIikgKyAKICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCAKICAgICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpXZSBhbGlnbiBldmVyeXRoaW5nIHVzaW5nIHRoZSBge3BhdGNod29ya31gIHBhY2thZ2UsIHNhdmUgdGhlIGZpZ3VyZSwgYW5kIHBsb3QgaXQgYWxsLiAKCmBgYHtyLCBmaWcud2lkdGg9MTMsIGZpZy5oZWlnaHQ9OH0KcDRhIDwtIChwMCB8IHAxKSAvIChwMiB8IHAzKSArIAogICAgICAgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKQpwNGIgPC0gKHA0YSB8IHBsb3RfdGFibGUpICsgCiAgICAgICBwbG90X2xheW91dChuY29sID0gMiwgd2lkdGhzID0gYygzLCAxKSkgKyAKICAgICAgIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9IHBhc3RlMCgiTWV0cmljcyBmb3IgTHVuZyBSZWZlcmVuY2UgRGF0YXNldCIpKQpnZ3NhdmUoZmlsZW5hbWUgPSAiUUNfbHVuZ19yZWZlcmVuY2UucGRmIiwKICAgICAgIHBsb3QgPSBwNGIsIAogICAgICAgZGV2aWNlID0gInBkZiIsIAogICAgICAgcGF0aCA9ICIvYmx1ZS9yYmFjaGVyL2oubGVhcnkvcmVwb3Mvc2NMQU5FX0FuYWx5c2lzL0ZpZ3VyZXMvUUNfUGxvdHMvIiwgCiAgICAgICB3aWR0aCA9IDEzLAogICAgICAgaGVpZ2h0ID0gOCwgCiAgICAgICB1bml0cyA9ICJpbiIsIAogICAgICAgZHBpID0gInJldGluYSIpCnA0YgpgYGAKCiMjIFNpbmdsZS1zdWJqZWN0CgpXZSBsb2FkIHRoZSBzaW5nbGUtc3ViamVjdCBzaW11bGF0ZWQgZGF0YXNldHMgaW50byBhIGxpc3QgJiB0dXJuIHRoZW0gaW50byBge1NldXJhdH1gIG9iamVjdHMgZm9yIHBsb3R0aW5nLiAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KIyAxMDAgY2VsbHMgCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18wMV9DRUxMU18xMDApCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18wNV9DRUxMU18xMDApCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18xMF9DRUxMU18xMDApCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18yMF9DRUxMU18xMDApCiMgNTAwIGNlbGxzIAp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMDFfQ0VMTFNfNTAwKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMDVfQ0VMTFNfNTAwKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMTBfQ0VMTFNfNTAwKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMjBfQ0VMTFNfNTAwKQojIDEsMDAwIGNlbGxzIAp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMDFfQ0VMTFNfMTAwMCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzA1X0NFTExTXzEwMDApCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18xMF9DRUxMU18xMDAwKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMjBfQ0VMTFNfMTAwMCkKIyAyLDUwMCBjZWxscwp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMDFfQ0VMTFNfMjUwMCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzA1X0NFTExTXzI1MDApCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18xMF9DRUxMU18yNTAwKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMjBfQ0VMTFNfMjUwMCkKIyA1LDAwMCBjZWxscyAKdGFyX2xvYWQobHVuZ19zaW1fREVHXzAxX0NFTExTXzUwMDApCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18wNV9DRUxMU181MDAwKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMTBfQ0VMTFNfNTAwMCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzIwX0NFTExTXzUwMDApCiMgY29lcmNlIHRvIGxpc3QgJiBwcm9jZXNzCm9ial9saXN0IDwtIHB1cnJyOjptYXAobHMocGF0dGVybiA9ICJsdW5nX3NpbSIpWyFncmVwbCgiYmFsYW5jZWQiLCBscyhwYXR0ZXJuID0gImx1bmdfc2ltIikpXSwgZnVuY3Rpb24oc2ltKSB7CiAgb2JqIDwtIGV2YWwoYXMuc3ltYm9sKHNpbSkpCiAgb2JqIDwtIGFzLlNldXJhdChvYmosIGNvdW50cyA9ICJjb3VudHMiLCBkYXRhID0gImxvZ2NvdW50cyIpCiAgb2JqQG1ldGEuZGF0YSA8LSBtdXRhdGUob2JqQG1ldGEuZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGVyY19kZWcgPSBwYXN0ZTAoYXMubnVtZXJpYyhzdHJpbmdyOjpzdHJfcmVtb3ZlKHN0cmluZ3I6OnN0cl9leHRyYWN0KHNpbSwgImx1bmdfc2ltX0RFR18uLiIpLCAibHVuZ19zaW1fREVHXyIpKSwgIiUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jZWxscyA9IGFzLmNoYXJhY3RlcihuY29sKG9iaikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBuX2dlbmVzID0gYXMuY2hhcmFjdGVyKG5yb3cob2JqKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgIHNjZV9uYW1lID0gc2ltKQogIHJldHVybihvYmopCn0pCnJtKGxpc3QgPSBscyhwYXR0ZXJuID0gImx1bmdfc2ltIikpOyBnYyhmdWxsID0gVFJVRSkKYGBgCgpJdGVyYXRpbmcgb3ZlciB0aGUgZGF0YXNldHMsIHdlIHByaW50IGVhY2ggUUMgcGxvdCAmIHNhdmUgdGhlbSB0byBQREZzLiAKCmBgYHtyLCBmaWcud2lkdGg9MTMsIGZpZy5oZWlnaHQ9OCwgcmVzdWx0cz0naG9sZCd9CnB1cnJyOjp3YWxrKG9ial9saXN0LCBmdW5jdGlvbih6KSB7CiAgIyBnYXRoZXIgbWV0YWRhdGEgCiAgb2JqX25hbWUgPC0gekBtZXRhLmRhdGEkc2NlX25hbWVbMV0KICBuX2NlbGxzIDwtIHpAbWV0YS5kYXRhJG5fY2VsbHNbMV0KICBuX2dlbmVzIDwtIHpAbWV0YS5kYXRhJG5fZ2VuZXNbMV0KICBwZXJjX2RlZyA8LSB6QG1ldGEuZGF0YSRwZXJjX2RlZ1sxXQogICMgc3VtbWFyeSBzdGF0IHRhYmxlIAogIHNwYXJzaXR5X2NvdW50IDwtIG1lYW4oekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzID09IDApCiAgbWVhbl9jb3VudCA8LSBtZWFuKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKICBtZWRfY291bnQgPC0gaWZlbHNlKHNwYXJzaXR5X2NvdW50ID4gMC41LCAwLCBtZWRpYW4oekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKSkKICBzZF9jb3VudCA8LSBzZCh6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpCiAgdmFyX2NvdW50IDwtIHNkX2NvdW50XjIgCiAgcmFuZ2VfY291bnQgPC0gcmFuZ2UoekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKQogIHN1bW1hcnlfZGYgPC0gZGF0YS5mcmFtZShtZXRyaWMgPSBjKCJNZWFuIiwgIk1lZGlhbiIsICJTLkQuIiwgIlZhcmlhbmNlIiwgIlJhbmdlIiwgIlNwYXJzaXR5IiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGMocm91bmQobWVhbl9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQobWVkX2NvdW50LCAyKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChzZF9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQodmFyX2NvdW50LCAyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgiKCIsIHJhbmdlX2NvdW50WzFdLCAiLCAiLCByYW5nZV9jb3VudFsyXSwgIikiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAocm91bmQoc3BhcnNpdHlfY291bnQsIDQpICogMTAwLCAiJSIpKSkKICAjIGNyZWF0ZSBjb3VudHMgaGlzdG9ncmFtCiAgcDAgPC0gZGF0YS5mcmFtZSh4ID0gYXMubnVtZXJpYyh6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpKSAlPiUgCiAgICAgICAgZ2dwbG90KGFlcyh4ID0geCkpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJkb2RnZXJibHVlIikgKyAKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9zY2llbnRpZmljKCkpICsgCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfY29tbWEoKSkgKyAKICAgICAgICBsYWJzKHggPSAiUmF3IEV4cHJlc3Npb24iLCB5ID0gIkZyZXF1ZW5jeSIpICsgCiAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkKICAjIGNyZWF0ZSBsb2cgY291bnRzIGhpc3RvZ3JhbQogIHAxIDwtIGRhdGEuZnJhbWUoeCA9IGFzLm51bWVyaWMoekBhc3NheXMkb3JpZ2luYWxleHBAZGF0YSkpICU+JSAKICAgICAgICBnZ3Bsb3QoYWVzKHggPSB4KSkgKyAKICAgICAgICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gImZvcmVzdGdyZWVuIikgKyAKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9zY2llbnRpZmljKCkpICsgCiAgICAgICAgbGFicyh4ID0gIk5vcm1hbGl6ZWQgRXhwcmVzc2lvbiIsIHkgPSAiRnJlcXVlbmN5IikgKyAKICAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KQogICMgY3JlYXRlIFVNQVAgYnkgY2x1c3RlciAKICBwMiA8LSBkYXRhLmZyYW1lKFVNQVAxID0gekByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzWywgMV0sIAogICAgICAgICAgICAgICAgICAgVU1BUDIgPSB6QHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3NbLCAyXSwgCiAgICAgICAgICAgICAgICAgICBjbHVzdGVyID0geiRsYWJlbCkgJT4lIAogICAgICAgIGdncGxvdChhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2x1c3RlcikpICsgCiAgICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3NjaTo6Y2F0ZWdvcnkyMF9kMyIpKSArIAogICAgICAgIGxhYnMoeCA9ICJVTUFQIDEiLCB5ID0gIlVNQVAgMiIsIGNvbG9yID0gIkxvdXZhaW4gQ2x1c3RlciIpICsgCiAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgICAgICAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIpKSkKICAjIGNyZWF0ZSBQQ0Egb2YgY2VsbCBvcmRlcmluZyAKICBwMyA8LSBkYXRhLmZyYW1lKFBDMSA9IHpAcmVkdWN0aW9ucyRQQ0FAY2VsbC5lbWJlZGRpbmdzWywgMV0sIAogICAgICAgICAgICAgICAgICAgUEMyID0gekByZWR1Y3Rpb25zJFBDQUBjZWxsLmVtYmVkZGluZ3NbLCAyXSwgCiAgICAgICAgICAgICAgICAgICBjZWxsX3RpbWUgPSB6JGNlbGxfdGltZV9ub3JtZWQpICU+JSAKICAgICAgICBnZ3Bsb3QoYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG9yID0gY2VsbF90aW1lKSkgKyAKICAgICAgICBnZW9tX3BvaW50KCkgKyAKICAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gcGFsZXR0ZWVyX2QoIndlc2FuZGVyc29uOjpaaXNzb3UxIikpICsgCiAgICAgICAgbGFicyh4ID0gIlBDIDEiLCB5ID0gIlBDIDIiLCBjb2xvciA9ICJUcnVlIE9yZGVyaW5nIikgKyAKICAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSArIAogICAgICAgIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKICAjIHRhYmxlIG9mIHNpbXVsYXRpb24gcGFyYW1ldGVycyAKICBwYXJhbV9kZiA8LSBkYXRhLmZyYW1lKG1ldHJpYyA9IGMoIk51bWJlciBvZiBDZWxscyIsICJOdW1iZXIgb2YgR2VuZXMiLCAiJSBEeW5hbWljIEdlbmVzIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBjKGFzLmNoYXJhY3RlcihuX2NlbGxzKSwgYXMuY2hhcmFjdGVyKG5fZ2VuZXMpLCBwZXJjX2RlZykpCiAgcGxvdF90YWJsZSA8LSByYmluZChzdW1tYXJ5X2RmLCBwYXJhbV9kZikgJT4lIAogICAgICAgICAgICAgICAgZ3JpZEV4dHJhOjp0YWJsZUdyb2Iocm93cyA9IE5VTEwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29scyA9IGMoIk1ldHJpYyIsICJWYWx1ZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lID0gZ3JpZEV4dHJhOjp0dGhlbWVfbWluaW1hbChjb3JlID0gbGlzdChmZ19wYXJhbXMgPSBsaXN0KGhqdXN0ID0gMCwgeCA9IDAuMDUpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29saGVhZCA9IGxpc3QoZmdfcGFyYW1zID0gbGlzdChoanVzdCA9IDAsIHggPSAwLjA1KSkpKSAlPiUgCiAgICAgICAgICAgICAgICBndGFibGU6Omd0YWJsZV9hZGRfZ3JvYihncm9icyA9IGdyaWQ6OnJlY3RHcm9iKGdwID0gZ3JpZDo6Z3BhcihmaWxsID0gTkEsIGx3ZCA9IDIpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ID0gMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiID0gbnJvdyguKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByID0gbmNvbCguKSkgJT4lIAogICAgICAgICAgICAgICAgZ3RhYmxlOjpndGFibGVfYWRkX2dyb2IoZ3JvYnMgPSBncmlkOjpyZWN0R3JvYihncCA9IGdyaWQ6OmdwYXIoZmlsbCA9IE5BLCBsd2QgPSAyKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgciA9IG5jb2woLikpCiAgIyBhbGlnbiBldmVyeXRoaW5nIAogIHA0YSA8LSAocDAgfCBwMSkgLyAocDIgfCBwMykgKyAKICAgICAgICAgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKQogIHA0YiA8LSAocDRhIHwgcGxvdF90YWJsZSkgKyAKICAgICAgICAgcGxvdF9sYXlvdXQobmNvbCA9IDIsIHdpZHRocyA9IGMoMywgMSkpICsgCiAgICAgICAgIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9IHBhc3RlMCgiTWV0cmljcyBmb3IgZGF0YXNldDogIiwgb2JqX25hbWUpKQogICMgc2F2ZSAmIHByaW50IHBsb3QKICBnZ3NhdmUoZmlsZW5hbWUgPSBwYXN0ZTAoIlFDXyIsIG9ial9uYW1lLCAiLnBkZiIpLAogICAgICAgICBwbG90ID0gcDRiLCAKICAgICAgICAgZGV2aWNlID0gInBkZiIsIAogICAgICAgICBwYXRoID0gIi9ibHVlL3JiYWNoZXIvai5sZWFyeS9yZXBvcy9zY0xBTkVfQW5hbHlzaXMvRmlndXJlcy9RQ19QbG90cy8iLCAKICAgICAgICAgd2lkdGggPSAxMywKICAgICAgICAgaGVpZ2h0ID0gOCwgCiAgICAgICAgIHVuaXRzID0gImluIiwgCiAgICAgICAgIGRwaSA9ICJyZXRpbmEiKQogIHByaW50KHA0YikKICAjIGNsZWFudXAgCiAgc2luayh0ZW1wZmlsZSgpKQogIHJtKHAwLCBwMSwgcDIsIHAzLCBwNGEsIHA0YiwgcGxvdF90YWJsZSk7IGdjKGZ1bGwgPSBUUlVFKQogIHNpbmsoKQp9KQpybShvYmpfbGlzdCkKYGBgCgojIyBNdWx0aS1zdWJqZWN0IAoKV2UgcmVwZWF0IHRoZSBwcm9jZXNzIGZvciB0aGUgbXVsdGktc3ViamVjdCBzaW11bGF0ZWQgZGF0YXNldHMuIAoKYGBge3IsIHJlc3VsdHM9J2hpZGUnfQojIDEwMCBjZWxscwp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMTBfQ0VMTFNfMTAwX2JhbGFuY2VkKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMjBfQ0VMTFNfMTAwX2JhbGFuY2VkKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMTBfQ0VMTFNfMTAwX3VuYmFsYW5jZWQpCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18yMF9DRUxMU18xMDBfdW5iYWxhbmNlZCkKIyA1MDAgY2VsbHMKdGFyX2xvYWQobHVuZ19zaW1fREVHXzEwX0NFTExTXzUwMF9iYWxhbmNlZCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzIwX0NFTExTXzUwMF9iYWxhbmNlZCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzEwX0NFTExTXzUwMF91bmJhbGFuY2VkKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMjBfQ0VMTFNfNTAwX3VuYmFsYW5jZWQpCiMgMSwwMDAgY2VsbHMgCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18xMF9DRUxMU18xMDAwX2JhbGFuY2VkKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMjBfQ0VMTFNfMTAwMF9iYWxhbmNlZCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzEwX0NFTExTXzEwMDBfdW5iYWxhbmNlZCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzIwX0NFTExTXzEwMDBfdW5iYWxhbmNlZCkKIyAyLDUwMCBjZWxscwp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMTBfQ0VMTFNfMjUwMF9iYWxhbmNlZCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzIwX0NFTExTXzI1MDBfYmFsYW5jZWQpCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18xMF9DRUxMU18yNTAwX3VuYmFsYW5jZWQpCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18yMF9DRUxMU18yNTAwX3VuYmFsYW5jZWQpCiMgNSwwMDAgY2VsbHMgCnRhcl9sb2FkKGx1bmdfc2ltX0RFR18xMF9DRUxMU181MDAwX2JhbGFuY2VkKQp0YXJfbG9hZChsdW5nX3NpbV9ERUdfMjBfQ0VMTFNfNTAwMF9iYWxhbmNlZCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzEwX0NFTExTXzUwMDBfdW5iYWxhbmNlZCkKdGFyX2xvYWQobHVuZ19zaW1fREVHXzIwX0NFTExTXzUwMDBfdW5iYWxhbmNlZCkKIyBjb2VyY2UgdG8gbGlzdCAmIHByb2Nlc3MKb2JqX2xpc3QgPC0gcHVycnI6Om1hcChscyhwYXR0ZXJuID0gImx1bmdfc2ltKmJhbGFuY2VkIiksIGZ1bmN0aW9uKHNpbSkgewogIG9iaiA8LSBldmFsKGFzLnN5bWJvbChzaW0pKQogIG9iaiA8LSBhcy5TZXVyYXQob2JqLCBjb3VudHMgPSAiY291bnRzIiwgZGF0YSA9ICJsb2djb3VudHMiKQogIG9iakBtZXRhLmRhdGEgPC0gbXV0YXRlKG9iakBtZXRhLmRhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfZGVnID0gcGFzdGUwKGFzLm51bWVyaWMoc3RyaW5ncjo6c3RyX3JlbW92ZShzdHJpbmdyOjpzdHJfZXh0cmFjdChzaW0sICJsdW5nX3NpbV9ERUdfLi4iKSwgImx1bmdfc2ltX0RFR18iKSksICIlIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgIG5fY2VsbHMgPSBhcy5jaGFyYWN0ZXIobmNvbChvYmopKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbl9nZW5lcyA9IGFzLmNoYXJhY3Rlcihucm93KG9iaikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBhbGxvY2F0aW9uID0gaWZlbHNlKGdyZXBsKCJfYmFsYW5jZWQiLCBzaW0pLCAiQmFsYW5jZWQiLCAiVW5iYWxhbmNlZCIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBzY2VfbmFtZSA9IHNpbSkKICByZXR1cm4ob2JqKQp9KQpybShsaXN0ID0gbHMocGF0dGVybiA9ICJsdW5nX3NpbSIpKTsgZ2MoZnVsbCA9IFRSVUUpCmBgYAoKV2UgYWRkIGEgVU1BUCBvZiB0aGUgc3ViamVjdCBpZGVudGl0aWVzLCAmIGEgUENBIHBsb3Qgb2YgdGhlIHRydWUgcHNldWRvdGltZSBzcGxpdCBieSBzdWJqZWN0IHdoZW4gZ2VuZXJhdGluZyB0aGUgUUMgcGxvdHMgZm9yIG11bHRpLXN1YmplY3QgZGF0YS4gCgpgYGB7ciwgZmlnLndpZHRoPTEzLCBmaWcuaGVpZ2h0PTksIHJlc3VsdHM9J2hvbGQnfQpwdXJycjo6d2FsayhvYmpfbGlzdCwgZnVuY3Rpb24oeikgewogICMgZ2F0aGVyIG1ldGFkYXRhIAogIG9ial9uYW1lIDwtIHpAbWV0YS5kYXRhJHNjZV9uYW1lWzFdCiAgbl9jZWxscyA8LSB6QG1ldGEuZGF0YSRuX2NlbGxzWzFdCiAgbl9nZW5lcyA8LSB6QG1ldGEuZGF0YSRuX2dlbmVzWzFdCiAgcGVyY19kZWcgPC0gekBtZXRhLmRhdGEkcGVyY19kZWdbMV0KICBhbGxvY2F0aW9uIDwtIHpAbWV0YS5kYXRhJGFsbG9jYXRpb25bMV0KICBuX3N1YmplY3RzIDwtIGxlbmd0aCh1bmlxdWUoekBtZXRhLmRhdGEkc3ViamVjdCkpCiAgIyBzdW1tYXJ5IHN0YXQgdGFibGUgCiAgc3BhcnNpdHlfY291bnQgPC0gbWVhbih6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMgPT0gMCkKICBtZWFuX2NvdW50IDwtIG1lYW4oekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKQogIG1lZF9jb3VudCA8LSBpZmVsc2Uoc3BhcnNpdHlfY291bnQgPiAwLjUsIDAsIG1lZGlhbih6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpKQogIHNkX2NvdW50IDwtIHNkKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKICB2YXJfY291bnQgPC0gc2RfY291bnReMiAKICByYW5nZV9jb3VudCA8LSByYW5nZSh6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpCiAgc3VtbWFyeV9kZiA8LSBkYXRhLmZyYW1lKG1ldHJpYyA9IGMoIk1lYW4iLCAiTWVkaWFuIiwgIlMuRC4iLCAiVmFyaWFuY2UiLCAiUmFuZ2UiLCAiU3BhcnNpdHkiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gYyhyb3VuZChtZWFuX2NvdW50LCAyKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChtZWRfY291bnQsIDIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKHNkX2NvdW50LCAyKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZCh2YXJfY291bnQsIDIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCIoIiwgcmFuZ2VfY291bnRbMV0sICIsICIsIHJhbmdlX2NvdW50WzJdLCAiKSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMChyb3VuZChzcGFyc2l0eV9jb3VudCwgNCkgKiAxMDAsICIlIikpKQogICMgY3JlYXRlIGNvdW50cyBoaXN0b2dyYW0KICBwMCA8LSBkYXRhLmZyYW1lKHggPSBhcy5udW1lcmljKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykpICU+JSAKICAgICAgICBnZ3Bsb3QoYWVzKHggPSB4KSkgKyAKICAgICAgICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gImRvZGdlcmJsdWUiKSArIAogICAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX3NjaWVudGlmaWMoKSkgKyAKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9jb21tYSgpKSArIAogICAgICAgIGxhYnMoeCA9ICJSYXcgRXhwcmVzc2lvbiIsIHkgPSAiRnJlcXVlbmN5IikgKyAKICAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KQogICMgY3JlYXRlIGxvZyBjb3VudHMgaGlzdG9ncmFtCiAgcDEgPC0gZGF0YS5mcmFtZSh4ID0gYXMubnVtZXJpYyh6QGFzc2F5cyRvcmlnaW5hbGV4cEBkYXRhKSkgJT4lIAogICAgICAgIGdncGxvdChhZXMoeCA9IHgpKSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGZpbGwgPSAiZm9yZXN0Z3JlZW4iKSArIAogICAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX3NjaWVudGlmaWMoKSkgKyAKICAgICAgICBsYWJzKHggPSAiTm9ybWFsaXplZCBFeHByZXNzaW9uIiwgeSA9ICJGcmVxdWVuY3kiKSArIAogICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpCiAgIyBjcmVhdGUgVU1BUCBieSBjbHVzdGVyICYgc3ViamVjdAogIGxlZ2VuZF9jbHVzdCA8LSBnZ3B1YnI6OmdldF9sZWdlbmQocCA9IChkYXRhLmZyYW1lKFVNQVAxID0gekByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzWywgMV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFVNQVAyID0gekByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzWywgMl0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXIgPSB6JGxhYmVsKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdncGxvdChhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2x1c3RlcikpICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdnc2NpOjpjYXRlZ29yeTIwX2QzIilbMTpsZW5ndGgodW5pcXVlKHokbGFiZWwpKV0pICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYnMoY29sb3IgPSAiTG91dmFpblxuQ2x1c3RlciIpICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgdGhlbWUobGVnZW5kLnRleHQuYWxpZ24gPSAwLjUpKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgbGVnZW5kX3N1YmogPC0gZ2dwdWJyOjpnZXRfbGVnZW5kKHAgPSAoZGF0YS5mcmFtZShVTUFQMSA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFVNQVAyID0gekByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzWywgMl0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ViamVjdCA9IHokc3ViamVjdCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdncGxvdChhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gc3ViamVjdCkpICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3NjaTo6Y2F0ZWdvcnkyMF9kMyIpWyhsZW5ndGgodW5pcXVlKHokbGFiZWwpKSArIDEpOjIwXSkgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJzKGNvbG9yID0gIlN1YmplY3QiKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpKSkKICBwMmEgPC0gZGF0YS5mcmFtZShVTUFQMSA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgICBVTUFQMiA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDJdLCAKICAgICAgICAgICAgICAgICAgICBjbHVzdGVyID0geiRsYWJlbCwgCiAgICAgICAgICAgICAgICAgICAgc3ViamVjdCA9IHokc3ViamVjdCkgJT4lIAogICAgICAgICAgIHRpZHlyOjpwaXZvdF9sb25nZXIoY29scyA9IGMoY2x1c3Rlciwgc3ViamVjdCksIG5hbWVzX3RvID0gImlkZW50IiwgdmFsdWVzX3RvID0gImlkZW50X3ZhbHVlIikgJT4lIAogICAgICAgICAgIG11dGF0ZShpZGVudCA9IGNhc2Vfd2hlbihpZGVudCA9PSAiY2x1c3RlciIgfiAiTG91dmFpbiBDbHVzdGVyIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAiU3ViamVjdCIpKSAlPiUgCiAgICAgICAgICAgZ2dwbG90KGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBpZGVudF92YWx1ZSwgZ3JvdXAgPSBpZGVudCkpICsgCiAgICAgICAgICAgZmFjZXRfd3JhcCh+aWRlbnQpICsgCiAgICAgICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3NjaTo6Y2F0ZWdvcnkyMF9kMyIpKSArIAogICAgICAgICAgIGxhYnMoeCA9ICJVTUFQIDEiLCB5ID0gIlVNQVAgMiIpICsgCiAgICAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIAogICAgICAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKICBwMmIgPC0gKHAyYSArICh3cmFwX2VsZW1lbnRzKGxlZ2VuZF9jbHVzdCkgLyB3cmFwX2VsZW1lbnRzKGxlZ2VuZF9zdWJqKSkgKyAKICAgICAgICAgcGxvdF9sYXlvdXQobmNvbCA9IDIsIHdpZHRocyA9IGMoNCwgMSkpKQogICMgY3JlYXRlIFBDQSBvZiBjZWxsIG9yZGVyaW5nIAogIHAzYSA8LSBkYXRhLmZyYW1lKFBDMSA9IHpAcmVkdWN0aW9ucyRQQ0FAY2VsbC5lbWJlZGRpbmdzWywgMV0sIAogICAgICAgICAgICAgICAgICAgIFBDMiA9IHpAcmVkdWN0aW9ucyRQQ0FAY2VsbC5lbWJlZGRpbmdzWywgMl0sIAogICAgICAgICAgICAgICAgICAgIGNlbGxfdGltZSA9IHokY2VsbF90aW1lX25vcm1lZCwgCiAgICAgICAgICAgICAgICAgICAgc3ViamVjdCA9IHokc3ViamVjdCkgJT4lIAogICAgICAgICBnZ3Bsb3QoYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG9yID0gY2VsbF90aW1lKSkgKyAKICAgICAgICAgZmFjZXRfd3JhcCh+c3ViamVjdCkgKyAKICAgICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSBwYWxldHRlZXJfZCgid2VzYW5kZXJzb246Olppc3NvdTEiKSkgKyAKICAgICAgICAgbGFicyh4ID0gIlBDIDEiLCB5ID0gIlBDIDIiLCBjb2xvciA9ICJUcnVlIE9yZGVyaW5nIikgKyAKICAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgICAgdGhlbWUoYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSkKICBwM2IgPC0gZGF0YS5mcmFtZShQQzEgPSB6QHJlZHVjdGlvbnMkUENBQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgICBQQzIgPSB6QHJlZHVjdGlvbnMkUENBQGNlbGwuZW1iZWRkaW5nc1ssIDJdLCAKICAgICAgICAgICAgICAgICAgICBjZWxsX3RpbWUgPSB6JGNlbGxfdGltZV9ub3JtZWQpICU+JSAKICAgICAgIGdncGxvdChhZXMoeCA9IFBDMSwgeSA9IFBDMiwgY29sb3IgPSBjZWxsX3RpbWUpKSArIAogICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gcGFsZXR0ZWVyX2QoIndlc2FuZGVyc29uOjpaaXNzb3UxIikpICsgCiAgICAgICBsYWJzKHggPSAiUEMgMSIsIHkgPSAiUEMgMiIsIGNvbG9yID0gIlRydWUgT3JkZXJpbmciKSArIAogICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpKQogIHAzYyA8LSAocDNhIHwgcDNiKSArIAogICAgICAgICBwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIsIHdpZHRocyA9IGMoMywgMiksIG5jb2wgPSAyKQogICMgdGFibGUgb2Ygc2ltdWxhdGlvbiBwYXJhbWV0ZXJzIAogIHBhcmFtX2RmIDwtIGRhdGEuZnJhbWUobWV0cmljID0gYygiTiBDZWxscyIsICJOIEdlbmVzIiwgIiUgRHluYW1pYyIsICJBbGxvY2F0aW9uIiwgIk4gU3ViamVjdHMiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGMoYXMuY2hhcmFjdGVyKG5fY2VsbHMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIobl9nZW5lcyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGVyY19kZWcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsbG9jYXRpb24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fc3ViamVjdHMpKQogIHBsb3RfdGFibGUgPC0gcmJpbmQoc3VtbWFyeV9kZiwgcGFyYW1fZGYpICU+JSAKICAgICAgICAgICAgICAgIGdyaWRFeHRyYTo6dGFibGVHcm9iKHJvd3MgPSBOVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJNZXRyaWMiLCAiVmFsdWUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZSA9IGdyaWRFeHRyYTo6dHRoZW1lX21pbmltYWwoY29yZSA9IGxpc3QoZmdfcGFyYW1zID0gbGlzdChoanVzdCA9IDAsIHggPSAwLjA1KSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGhlYWQgPSBsaXN0KGZnX3BhcmFtcyA9IGxpc3QoaGp1c3QgPSAwLCB4ID0gMC4wNSkpKSkgJT4lIAogICAgICAgICAgICAgICAgZ3RhYmxlOjpndGFibGVfYWRkX2dyb2IoZ3JvYnMgPSBncmlkOjpyZWN0R3JvYihncCA9IGdyaWQ6OmdwYXIoZmlsbCA9IE5BLCBsd2QgPSAyKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdCA9IDIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYiA9IG5yb3coLiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgciA9IG5jb2woLikpICU+JSAKICAgICAgICAgICAgICAgIGd0YWJsZTo6Z3RhYmxlX2FkZF9ncm9iKGdyb2JzID0gZ3JpZDo6cmVjdEdyb2IoZ3AgPSBncmlkOjpncGFyKGZpbGwgPSBOQSwgbHdkID0gMikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHQgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGwgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHIgPSBuY29sKC4pKQogICMgYWxpZ24gZXZlcnl0aGluZyAKICBwNGEgPC0gKChwMCB8IHAxIHwgcGxvdF90YWJsZSkgKyBwbG90X2xheW91dCh3aWR0aHMgPSBjKDEsIDEsIDAuNSkpKSAvIChwMmIgfCBwM2MpICsgCiAgICAgICAgIHBsb3RfbGF5b3V0KG5yb3cgPSAyKSArIAogICAgICAgICBwbG90X2Fubm90YXRpb24odGl0bGUgPSBwYXN0ZTAoIk1ldHJpY3MgZm9yIGRhdGFzZXQ6ICIsIG9ial9uYW1lKSkKICAjIHNhdmUgJiBwcmludCBwbG90CiAgZ2dzYXZlKGZpbGVuYW1lID0gcGFzdGUwKCJRQ18iLCBvYmpfbmFtZSwgIi5wZGYiKSwKICAgICAgICAgcGxvdCA9IHA0YSwgCiAgICAgICAgIGRldmljZSA9ICJwZGYiLCAKICAgICAgICAgcGF0aCA9ICIvYmx1ZS9yYmFjaGVyL2oubGVhcnkvcmVwb3Mvc2NMQU5FX0FuYWx5c2lzL0ZpZ3VyZXMvUUNfUGxvdHMvIiwgCiAgICAgICAgIHdpZHRoID0gMTMsCiAgICAgICAgIGhlaWdodCA9IDksIAogICAgICAgICB1bml0cyA9ICJpbiIsIAogICAgICAgICBkcGkgPSAicmV0aW5hIikKICBwcmludChwNGEpCiAgIyBjbGVhbnVwIAogIHNpbmsodGVtcGZpbGUoKSkKICBybShwMCwgcDEsIHAyLCBwMywgcDRhLCBwbG90X3RhYmxlKTsgZ2MoZnVsbCA9IFRSVUUpCiAgc2luaygpCn0pCnJtKG9ial9saXN0KQpgYGAKCiMgUGFuY3JlYXMgUmVmZXJlbmNlIAoKTmV4dCBsZXQncyBleGFtaW5lIHRoZSBwYW5jcmVhcyByZWZlcmVuY2UgZGF0YXNldC4gU2luY2UgaXQncyBzbWFsbGVyIHRoYW4gdGhlIGx1bmcgcmVmZXJlbmNlLCB3ZSBkb24ndCBuZWVkIHRvIHJlc29ydCB0byB0cmlja2VyeSB0byBnZW5lcmF0ZSBvdXIgaGlzdG9ncmFtcy4gCgpgYGB7cn0KcGFuY19kYXRhIDwtIHNjUk5Bc2VxOjpCYXJvblBhbmNyZWFzRGF0YSh3aGljaCA9ICJodW1hbiIpCnBhbmNfZGF0YV9jbGVhbiA8LSBwYW5jX2RhdGFbcm93U3Vtcyhjb3VudHMocGFuY19kYXRhKSA+IDApID49IDMsIF0gICMgZ2VuZXMgaW4gYXQgbGVhc3QgMyBjZWxscwpwYW5jX2RhdGFfY2xlYW4gPC0gbG9nTm9ybUNvdW50cyhwYW5jX2RhdGFfY2xlYW4pCnZhcl9kZWNvbXAgPC0gbW9kZWxHZW5lVmFyKHBhbmNfZGF0YV9jbGVhbikKdG9wMmtfaHZncyA8LSBnZXRUb3BIVkdzKHZhcl9kZWNvbXAsIG4gPSAyMDAwKQpwYW5jX2RhdGFfY2xlYW4gPC0gcnVuUENBKHBhbmNfZGF0YV9jbGVhbiwgc3Vic2V0X3JvdyA9IHRvcDJrX2h2Z3MpCnJlZHVjZWREaW0ocGFuY19kYXRhX2NsZWFuLCAiUENBc3ViIikgPC0gcmVkdWNlZERpbShwYW5jX2RhdGFfY2xlYW4sICJQQ0EiKVssIDE6MzAsIGRyb3AgPSBGQUxTRV0KcGFuY19kYXRhX2NsZWFuIDwtIHJ1blVNQVAocGFuY19kYXRhX2NsZWFuLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gIlBDQXN1YiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX2RpbXJlZCA9IDE6MzApCmcgPC0gYnVpbGRTTk5HcmFwaChwYW5jX2RhdGFfY2xlYW4sCiAgICAgICAgICAgICAgICAgICB1c2UuZGltcmVkID0gIlBDQXN1YiIsCiAgICAgICAgICAgICAgICAgICBrID0gMzApCmNsdXN0ZXJzIDwtIGlncmFwaDo6Y2x1c3Rlcl9sb3V2YWluKGdyYXBoID0gZykkbWVtYmVyc2hpcApjb2xMYWJlbHMocGFuY19kYXRhX2NsZWFuKSA8LSBmYWN0b3IoY2x1c3RlcnMpCnBhbmNfZGF0YV9jbGVhbiA8LSBhcy5TZXVyYXQocGFuY19kYXRhX2NsZWFuLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3VudHMgPSAiY291bnRzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9ICJsb2djb3VudHMiKQpgYGAKCldlIHJlLWNyZWF0ZSB0aGUgc3VtbWFyeSBzdGF0aXN0aWNzIHRhYmxlIGZvciB0aGUgcGFuY3JlYXMgcmVmZXJlbmNlLiAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30Kbl9jZWxscyA8LSBuY29sKHBhbmNfZGF0YV9jbGVhbikKbl9nZW5lcyA8LSBucm93KHBhbmNfZGF0YV9jbGVhbikKbWVhbl9jb3VudCA8LSBtZWFuKHBhbmNfZGF0YV9jbGVhbkBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKQptZWRfY291bnQgPC0gMApzZF9jb3VudCA8LSBzZChwYW5jX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKdmFyX2NvdW50IDwtIHNkX2NvdW50XjIgICMgZmFzdGVyIApyYW5nZV9jb3VudCA8LSByYW5nZShwYW5jX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKc3BhcnNpdHlfY291bnQgPC0gbWVhbihwYW5jX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cyA9PSAwKQpzdW1tYXJ5X2RmIDwtIGRhdGEuZnJhbWUobWV0cmljID0gYygiTWVhbiIsICJNZWRpYW4iLCAiUy5ELiIsICJWYXJpYW5jZSIsICJSYW5nZSIsICJTcGFyc2l0eSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gYyhyb3VuZChtZWFuX2NvdW50LCAyKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVkX2NvdW50LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChzZF9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKHZhcl9jb3VudCwgMiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCIoIiwgcmFuZ2VfY291bnRbMV0sICIsICIsIHJhbmdlX2NvdW50WzJdLCAiKSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAocm91bmQoc3BhcnNpdHlfY291bnQsIDQpICogMTAwLCAiJSIpKSkKcGxvdF90YWJsZSA8LSBncmlkRXh0cmE6OnRhYmxlR3JvYihzdW1tYXJ5X2RmLCByb3dzID0gTlVMTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29scyA9IGMoIk1ldHJpYyIsICJWYWx1ZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZSA9IGdyaWRFeHRyYTo6dHRoZW1lX21pbmltYWwoY29yZSA9IGxpc3QoZmdfcGFyYW1zID0gbGlzdChoanVzdCA9IDAsIHggPSAwLjA1KSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xoZWFkID0gbGlzdChmZ19wYXJhbXMgPSBsaXN0KGhqdXN0ID0gMCwgeCA9IDAuMDUpKSkpICU+JSAKICAgICAgICAgICAgICBndGFibGU6Omd0YWJsZV9hZGRfZ3JvYihncm9icyA9IGdyaWQ6OnJlY3RHcm9iKGdwID0gZ3JpZDo6Z3BhcihmaWxsID0gTkEsIGx3ZCA9IDIpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdCA9IDIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGIgPSBucm93KC4pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgciA9IG5jb2woLikpICU+JSAKICAgICAgICAgICAgICBndGFibGU6Omd0YWJsZV9hZGRfZ3JvYihncm9icyA9IGdyaWQ6OnJlY3RHcm9iKGdwID0gZ3JpZDo6Z3BhcihmaWxsID0gTkEsIGx3ZCA9IDIpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGwgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByID0gbmNvbCguKSkKZ2MoZnVsbCA9IFRSVUUpCmBgYAoKV2UgY3JlYXRlIHRoZSB0d28gaGlzdG9ncmFtcyBpbiB0aGUgdXN1YWwgd2F5LgoKYGBge3J9CnAwIDwtIGRhdGEuZnJhbWUoeCA9IGFzLm51bWVyaWMocGFuY19kYXRhX2NsZWFuQGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpKSAlPiUgCiAgICAgIGdncGxvdChhZXMoeCA9IHgpKSArIAogICAgICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gImRvZGdlcmJsdWUiKSArIAogICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9zY2llbnRpZmljKCkpICsgCiAgICAgIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX2NvbW1hKCkpICsgCiAgICAgIGxhYnMoeCA9ICJSYXcgRXhwcmVzc2lvbiIsIHkgPSAiRnJlcXVlbmN5IikgKyAKICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkKcDEgPC0gZGF0YS5mcmFtZSh4ID0gYXMubnVtZXJpYyhwYW5jX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGRhdGEpKSAlPiUgCiAgICAgIGdncGxvdChhZXMoeCA9IHgpKSArIAogICAgICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gImZvcmVzdGdyZWVuIikgKyAKICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfc2NpZW50aWZpYygpKSArIAogICAgICBsYWJzKHggPSAiTm9ybWFsaXplZCBFeHByZXNzaW9uIiwgeSA9ICJGcmVxdWVuY3kiKSArIAogICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KQpgYGAKClRoZSBVTUFQICYgUENBIHBsb3RzIGFyZSBnZW5lcmF0ZWQgYXMgYmVmb3JlLiAKCmBgYHtyfQpwMiA8LSBkYXRhLmZyYW1lKFVNQVAxID0gcGFuY19kYXRhX2NsZWFuQHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3NbLCAxXSwgCiAgICAgICAgICAgICAgICAgVU1BUDIgPSBwYW5jX2RhdGFfY2xlYW5AcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDJdLCAKICAgICAgICAgICAgICAgICBjbHVzdGVyID0gcGFuY19kYXRhX2NsZWFuJGxhYmVsKSAlPiUgCiAgICAgIGdncGxvdChhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2x1c3RlcikpICsgCiAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdnc2NpOjpjYXRlZ29yeTIwX2QzIikpICsgCiAgICAgIGxhYnMoeCA9ICJVTUFQIDEiLCB5ID0gIlVNQVAgMiIsIGNvbG9yID0gIkxvdXZhaW4gQ2x1c3RlciIpICsgCiAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgICAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIpKSkKcDMgPC0gZGF0YS5mcmFtZShQQzEgPSBwYW5jX2RhdGFfY2xlYW5AcmVkdWN0aW9ucyRQQ0FAY2VsbC5lbWJlZGRpbmdzWywgMV0sIAogICAgICAgICAgICAgICAgIFBDMiA9IHBhbmNfZGF0YV9jbGVhbkByZWR1Y3Rpb25zJFBDQUBjZWxsLmVtYmVkZGluZ3NbLCAyXSwgCiAgICAgICAgICAgICAgICAgY2x1c3RlciA9IHBhbmNfZGF0YV9jbGVhbiRsYWJlbCkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG9yID0gY2x1c3RlcikpICsgCiAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdnc2NpOjpjYXRlZ29yeTIwX2QzIikpICsgCiAgICAgIGxhYnMoeCA9ICJQQyAxIiwgeSA9ICJQQyAyIiwgY29sb3IgPSAiTG91dmFpbiBDbHVzdGVyIikgKyAKICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCAKICAgICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpMYXN0bHksIHdlIGFsaWduIGV2ZXJ5dGhpbmcgJiBwbG90IGl0LiAKCmBgYHtyLCBmaWcud2lkdGg9MTMsIGZpZy5oZWlnaHQ9OH0KcDRhIDwtIChwMCB8IHAxKSAvIChwMiB8IHAzKSArIAogICAgICAgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKQpwNGIgPC0gKHA0YSB8IHBsb3RfdGFibGUpICsgCiAgICAgICBwbG90X2xheW91dChuY29sID0gMiwgd2lkdGhzID0gYygzLCAxKSkgKyAKICAgICAgIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9IHBhc3RlMCgiTWV0cmljcyBmb3IgUGFuY3JlYXMgUmVmZXJlbmNlIERhdGFzZXQiKSkKZ2dzYXZlKGZpbGVuYW1lID0gIlFDX3BhbmNfcmVmZXJlbmNlLnBkZiIsCiAgICAgICBwbG90ID0gcDRiLCAKICAgICAgIGRldmljZSA9ICJwZGYiLCAKICAgICAgIHBhdGggPSAiL2JsdWUvcmJhY2hlci9qLmxlYXJ5L3JlcG9zL3NjTEFORV9BbmFseXNpcy9GaWd1cmVzL1FDX1Bsb3RzLyIsIAogICAgICAgd2lkdGggPSAxMywKICAgICAgIGhlaWdodCA9IDgsIAogICAgICAgdW5pdHMgPSAiaW4iLCAKICAgICAgIGRwaSA9ICJyZXRpbmEiKQpwNGIKYGBgCgojIyBTaW5nbGUtc3ViamVjdAoKSGVyZSB3ZSBicmluZyB0aGUgc2luZ2xlIHN1YmplY3Qgc2ltdWxhdGlvbnMgZnJvbSB0aGUgcGFuY3JlYXMgcmVmZXJlbmNlIGludG8gbWVtb3J5LiAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KIyAxMDAgY2VsbHMgCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18wMV9DRUxMU18xMDApCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18wNV9DRUxMU18xMDApCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18xMF9DRUxMU18xMDApCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18yMF9DRUxMU18xMDApCiMgNTAwIGNlbGxzIAp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMDFfQ0VMTFNfNTAwKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMDVfQ0VMTFNfNTAwKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMTBfQ0VMTFNfNTAwKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMjBfQ0VMTFNfNTAwKQojIDEsMDAwIGNlbGxzIAp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMDFfQ0VMTFNfMTAwMCkKdGFyX2xvYWQocGFuY19zaW1fREVHXzA1X0NFTExTXzEwMDApCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18xMF9DRUxMU18xMDAwKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMjBfQ0VMTFNfMTAwMCkKIyAyLDUwMCBjZWxscwp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMDFfQ0VMTFNfMjUwMCkKdGFyX2xvYWQocGFuY19zaW1fREVHXzA1X0NFTExTXzI1MDApCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18xMF9DRUxMU18yNTAwKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMjBfQ0VMTFNfMjUwMCkKIyA1LDAwMCBjZWxscyAKdGFyX2xvYWQocGFuY19zaW1fREVHXzAxX0NFTExTXzUwMDApCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18wNV9DRUxMU181MDAwKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMTBfQ0VMTFNfNTAwMCkKdGFyX2xvYWQocGFuY19zaW1fREVHXzIwX0NFTExTXzUwMDApCiMgY29lcmNlIHRvIGxpc3QgJiBwcm9jZXNzCm9ial9saXN0IDwtIHB1cnJyOjptYXAobHMocGF0dGVybiA9ICJwYW5jX3NpbSIpWyFncmVwbCgiYmFsYW5jZWQiLCBscyhwYXR0ZXJuID0gInBhbmNfc2ltIikpXSwgZnVuY3Rpb24oc2ltKSB7CiAgb2JqIDwtIGV2YWwoYXMuc3ltYm9sKHNpbSkpCiAgb2JqIDwtIGFzLlNldXJhdChvYmosIGNvdW50cyA9ICJjb3VudHMiLCBkYXRhID0gImxvZ2NvdW50cyIpCiAgb2JqQG1ldGEuZGF0YSA8LSBtdXRhdGUob2JqQG1ldGEuZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGVyY19kZWcgPSBwYXN0ZTAoYXMubnVtZXJpYyhzdHJpbmdyOjpzdHJfcmVtb3ZlKHN0cmluZ3I6OnN0cl9leHRyYWN0KHNpbSwgInBhbmNfc2ltX0RFR18uLiIpLCAicGFuY19zaW1fREVHXyIpKSwgIiUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jZWxscyA9IGFzLmNoYXJhY3RlcihuY29sKG9iaikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBuX2dlbmVzID0gYXMuY2hhcmFjdGVyKG5yb3cob2JqKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgIHNjZV9uYW1lID0gc2ltKQogIHJldHVybihvYmopCn0pCnJtKGxpc3QgPSBscyhwYXR0ZXJuID0gInBhbmNfc2ltIikpOyBnYyhmdWxsID0gVFJVRSkKYGBgCgpMZXQncyBnZW5lcmF0ZSB0aGUgcGxvdHMgZm9yIGVhY2ggZGF0YXNldC4gCgpgYGB7ciwgZmlnLndpZHRoPTEzLCBmaWcuaGVpZ2h0PTgsIHJlc3VsdHM9J2hvbGQnfQpwdXJycjo6d2FsayhvYmpfbGlzdCwgZnVuY3Rpb24oeikgewogICMgZ2F0aGVyIG1ldGFkYXRhIAogIG9ial9uYW1lIDwtIHpAbWV0YS5kYXRhJHNjZV9uYW1lWzFdCiAgbl9jZWxscyA8LSB6QG1ldGEuZGF0YSRuX2NlbGxzWzFdCiAgbl9nZW5lcyA8LSB6QG1ldGEuZGF0YSRuX2dlbmVzWzFdCiAgcGVyY19kZWcgPC0gekBtZXRhLmRhdGEkcGVyY19kZWdbMV0KICAjIHN1bW1hcnkgc3RhdCB0YWJsZSAKICBzcGFyc2l0eV9jb3VudCA8LSBtZWFuKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cyA9PSAwKQogIG1lYW5fY291bnQgPC0gbWVhbih6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpCiAgbWVkX2NvdW50IDwtIGlmZWxzZShzcGFyc2l0eV9jb3VudCA+IDAuNSwgMCwgbWVkaWFuKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykpCiAgc2RfY291bnQgPC0gc2QoekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKQogIHZhcl9jb3VudCA8LSBzZF9jb3VudF4yIAogIHJhbmdlX2NvdW50IDwtIHJhbmdlKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKICBzdW1tYXJ5X2RmIDwtIGRhdGEuZnJhbWUobWV0cmljID0gYygiTWVhbiIsICJNZWRpYW4iLCAiUy5ELiIsICJWYXJpYW5jZSIsICJSYW5nZSIsICJTcGFyc2l0eSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBjKHJvdW5kKG1lYW5fY291bnQsIDIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKG1lZF9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoc2RfY291bnQsIDIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKHZhcl9jb3VudCwgMiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoIigiLCByYW5nZV9jb3VudFsxXSwgIiwgIiwgcmFuZ2VfY291bnRbMl0sICIpIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKHJvdW5kKHNwYXJzaXR5X2NvdW50LCA0KSAqIDEwMCwgIiUiKSkpCiAgIyBjcmVhdGUgY291bnRzIGhpc3RvZ3JhbQogIHAwIDwtIGRhdGEuZnJhbWUoeCA9IGFzLm51bWVyaWMoekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKSkgJT4lIAogICAgICAgIGdncGxvdChhZXMoeCA9IHgpKSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGZpbGwgPSAiZG9kZ2VyYmx1ZSIpICsgCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfc2NpZW50aWZpYygpKSArIAogICAgICAgIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX2NvbW1hKCkpICsgCiAgICAgICAgbGFicyh4ID0gIlJhdyBFeHByZXNzaW9uIiwgeSA9ICJGcmVxdWVuY3kiKSArIAogICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpCiAgIyBjcmVhdGUgbG9nIGNvdW50cyBoaXN0b2dyYW0KICBwMSA8LSBkYXRhLmZyYW1lKHggPSBhcy5udW1lcmljKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGRhdGEpKSAlPiUgCiAgICAgICAgZ2dwbG90KGFlcyh4ID0geCkpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJmb3Jlc3RncmVlbiIpICsgCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfc2NpZW50aWZpYygpKSArIAogICAgICAgIGxhYnMoeCA9ICJOb3JtYWxpemVkIEV4cHJlc3Npb24iLCB5ID0gIkZyZXF1ZW5jeSIpICsgCiAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkKICAjIGNyZWF0ZSBVTUFQIGJ5IGNsdXN0ZXIgCiAgcDIgPC0gZGF0YS5mcmFtZShVTUFQMSA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgIFVNQVAyID0gekByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzWywgMl0sIAogICAgICAgICAgICAgICAgICAgY2x1c3RlciA9IHokbGFiZWwpICU+JSAKICAgICAgICBnZ3Bsb3QoYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGNsdXN0ZXIpKSArIAogICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmNhdGVnb3J5MjBfZDMiKSkgKyAKICAgICAgICBsYWJzKHggPSAiVU1BUCAxIiwgeSA9ICJVTUFQIDIiLCBjb2xvciA9ICJMb3V2YWluIENsdXN0ZXIiKSArIAogICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgICAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSArIAogICAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyKSkpCiAgIyBjcmVhdGUgUENBIG9mIGNlbGwgb3JkZXJpbmcgCiAgcDMgPC0gZGF0YS5mcmFtZShQQzEgPSB6QHJlZHVjdGlvbnMkUENBQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgIFBDMiA9IHpAcmVkdWN0aW9ucyRQQ0FAY2VsbC5lbWJlZGRpbmdzWywgMl0sIAogICAgICAgICAgICAgICAgICAgY2VsbF90aW1lID0geiRjZWxsX3RpbWVfbm9ybWVkKSAlPiUgCiAgICAgICAgZ2dwbG90KGFlcyh4ID0gUEMxLCB5ID0gUEMyLCBjb2xvciA9IGNlbGxfdGltZSkpICsgCiAgICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHBhbGV0dGVlcl9kKCJ3ZXNhbmRlcnNvbjo6Wmlzc291MSIpKSArIAogICAgICAgIGxhYnMoeCA9ICJQQyAxIiwgeSA9ICJQQyAyIiwgY29sb3IgPSAiVHJ1ZSBPcmRlcmluZyIpICsgCiAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpCiAgIyB0YWJsZSBvZiBzaW11bGF0aW9uIHBhcmFtZXRlcnMgCiAgcGFyYW1fZGYgPC0gZGF0YS5mcmFtZShtZXRyaWMgPSBjKCJOdW1iZXIgb2YgQ2VsbHMiLCAiTnVtYmVyIG9mIEdlbmVzIiwgIiUgRHluYW1pYyBHZW5lcyIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gYyhhcy5jaGFyYWN0ZXIobl9jZWxscyksIGFzLmNoYXJhY3RlcihuX2dlbmVzKSwgcGVyY19kZWcpKQogIHBsb3RfdGFibGUgPC0gcmJpbmQoc3VtbWFyeV9kZiwgcGFyYW1fZGYpICU+JSAKICAgICAgICAgICAgICAgIGdyaWRFeHRyYTo6dGFibGVHcm9iKHJvd3MgPSBOVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJNZXRyaWMiLCAiVmFsdWUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZSA9IGdyaWRFeHRyYTo6dHRoZW1lX21pbmltYWwoY29yZSA9IGxpc3QoZmdfcGFyYW1zID0gbGlzdChoanVzdCA9IDAsIHggPSAwLjA1KSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGhlYWQgPSBsaXN0KGZnX3BhcmFtcyA9IGxpc3QoaGp1c3QgPSAwLCB4ID0gMC4wNSkpKSkgJT4lIAogICAgICAgICAgICAgICAgZ3RhYmxlOjpndGFibGVfYWRkX2dyb2IoZ3JvYnMgPSBncmlkOjpyZWN0R3JvYihncCA9IGdyaWQ6OmdwYXIoZmlsbCA9IE5BLCBsd2QgPSAyKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdCA9IDIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYiA9IG5yb3coLiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgciA9IG5jb2woLikpICU+JSAKICAgICAgICAgICAgICAgIGd0YWJsZTo6Z3RhYmxlX2FkZF9ncm9iKGdyb2JzID0gZ3JpZDo6cmVjdEdyb2IoZ3AgPSBncmlkOjpncGFyKGZpbGwgPSBOQSwgbHdkID0gMikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHQgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGwgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHIgPSBuY29sKC4pKQogICMgYWxpZ24gZXZlcnl0aGluZyAKICBwNGEgPC0gKHAwIHwgcDEpIC8gKHAyIHwgcDMpICsgCiAgICAgICAgIHBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikKICBwNGIgPC0gKHA0YSB8IHBsb3RfdGFibGUpICsgCiAgICAgICAgIHBsb3RfbGF5b3V0KG5jb2wgPSAyLCB3aWR0aHMgPSBjKDMsIDEpKSArIAogICAgICAgICBwbG90X2Fubm90YXRpb24odGl0bGUgPSBwYXN0ZTAoIk1ldHJpY3MgZm9yIGRhdGFzZXQ6ICIsIG9ial9uYW1lKSkKICAjIHNhdmUgJiBwcmludCBwbG90CiAgZ2dzYXZlKGZpbGVuYW1lID0gcGFzdGUwKCJRQ18iLCBvYmpfbmFtZSwgIi5wZGYiKSwKICAgICAgICAgcGxvdCA9IHA0YiwgCiAgICAgICAgIGRldmljZSA9ICJwZGYiLCAKICAgICAgICAgcGF0aCA9ICIvYmx1ZS9yYmFjaGVyL2oubGVhcnkvcmVwb3Mvc2NMQU5FX0FuYWx5c2lzL0ZpZ3VyZXMvUUNfUGxvdHMvIiwgCiAgICAgICAgIHdpZHRoID0gMTMsCiAgICAgICAgIGhlaWdodCA9IDgsIAogICAgICAgICB1bml0cyA9ICJpbiIsIAogICAgICAgICBkcGkgPSAicmV0aW5hIikKICBwcmludChwNGIpCiAgIyBjbGVhbnVwIAogIHNpbmsodGVtcGZpbGUoKSkKICBybShwMCwgcDEsIHAyLCBwMywgcDRhLCBwNGIsIHBsb3RfdGFibGUpOyBnYyhmdWxsID0gVFJVRSkKICBzaW5rKCkKfSkKcm0ob2JqX2xpc3QpCmBgYAoKIyMgTXVsdGktc3ViamVjdCAKCldlIGxvYWQgaW4gdGhlIG11bHRpLXN1YmplY3Qgc2ltdWxhdGVkIGRhdGFzZXRzIGZyb20gdGhlIHBhbmNyZWFzIHJlZmVyZW5jZS4gCgpgYGB7ciwgcmVzdWx0cz0naGlkZSd9CiMgMTAwIGNlbGxzCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18xMF9DRUxMU18xMDBfYmFsYW5jZWQpCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18yMF9DRUxMU18xMDBfYmFsYW5jZWQpCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18xMF9DRUxMU18xMDBfdW5iYWxhbmNlZCkKdGFyX2xvYWQocGFuY19zaW1fREVHXzIwX0NFTExTXzEwMF91bmJhbGFuY2VkKQojIDUwMCBjZWxscwp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMTBfQ0VMTFNfNTAwX2JhbGFuY2VkKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMjBfQ0VMTFNfNTAwX2JhbGFuY2VkKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMTBfQ0VMTFNfNTAwX3VuYmFsYW5jZWQpCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18yMF9DRUxMU181MDBfdW5iYWxhbmNlZCkKIyAxLDAwMCBjZWxscyAKdGFyX2xvYWQocGFuY19zaW1fREVHXzEwX0NFTExTXzEwMDBfYmFsYW5jZWQpCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18yMF9DRUxMU18xMDAwX2JhbGFuY2VkKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMTBfQ0VMTFNfMTAwMF91bmJhbGFuY2VkKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMjBfQ0VMTFNfMTAwMF91bmJhbGFuY2VkKQojIDIsNTAwIGNlbGxzCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18xMF9DRUxMU18yNTAwX2JhbGFuY2VkKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMjBfQ0VMTFNfMjUwMF9iYWxhbmNlZCkKdGFyX2xvYWQocGFuY19zaW1fREVHXzEwX0NFTExTXzI1MDBfdW5iYWxhbmNlZCkKdGFyX2xvYWQocGFuY19zaW1fREVHXzIwX0NFTExTXzI1MDBfdW5iYWxhbmNlZCkKIyA1LDAwMCBjZWxscyAKdGFyX2xvYWQocGFuY19zaW1fREVHXzEwX0NFTExTXzUwMDBfYmFsYW5jZWQpCnRhcl9sb2FkKHBhbmNfc2ltX0RFR18yMF9DRUxMU181MDAwX2JhbGFuY2VkKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMTBfQ0VMTFNfNTAwMF91bmJhbGFuY2VkKQp0YXJfbG9hZChwYW5jX3NpbV9ERUdfMjBfQ0VMTFNfNTAwMF91bmJhbGFuY2VkKQojIGNvZXJjZSB0byBsaXN0ICYgcHJvY2VzcwpvYmpfbGlzdCA8LSBwdXJycjo6bWFwKGxzKHBhdHRlcm4gPSAicGFuY19zaW1fKmJhbGFuY2VkIiksIGZ1bmN0aW9uKHNpbSkgewogIG9iaiA8LSBldmFsKGFzLnN5bWJvbChzaW0pKQogIG9iaiA8LSBhcy5TZXVyYXQob2JqLCBjb3VudHMgPSAiY291bnRzIiwgZGF0YSA9ICJsb2djb3VudHMiKQogIG9iakBtZXRhLmRhdGEgPC0gbXV0YXRlKG9iakBtZXRhLmRhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfZGVnID0gcGFzdGUwKGFzLm51bWVyaWMoc3RyaW5ncjo6c3RyX3JlbW92ZShzdHJpbmdyOjpzdHJfZXh0cmFjdChzaW0sICJwYW5jX3NpbV9ERUdfLi4iKSwgInBhbmNfc2ltX0RFR18iKSksICIlIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgIG5fY2VsbHMgPSBhcy5jaGFyYWN0ZXIobmNvbChvYmopKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbl9nZW5lcyA9IGFzLmNoYXJhY3Rlcihucm93KG9iaikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBhbGxvY2F0aW9uID0gaWZlbHNlKGdyZXBsKCJfYmFsYW5jZWQiLCBzaW0pLCAiQmFsYW5jZWQiLCAiVW5iYWxhbmNlZCIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBzY2VfbmFtZSA9IHNpbSkKICByZXR1cm4ob2JqKQp9KQpybShsaXN0ID0gbHMocGF0dGVybiA9ICJwYW5jX3NpbSIpKTsgZ2MoZnVsbCA9IFRSVUUpCmBgYAoKV2UgcmVnZW5lcmF0ZSB0aGUgc2xpZ2h0bHkgbW9yZSBjb21wbGV4IG11bHRpLXN1YmplY3QgZmlndXJlcyBhcyB3ZSBkaWQgZm9yIHRoZSBsdW5nIGRhdGFzZXQuIAoKYGBge3IsIGZpZy53aWR0aD0xMywgZmlnLmhlaWdodD05LCByZXN1bHRzPSdob2xkJ30KcHVycnI6OndhbGsob2JqX2xpc3QsIGZ1bmN0aW9uKHopIHsKICAjIGdhdGhlciBtZXRhZGF0YSAKICBvYmpfbmFtZSA8LSB6QG1ldGEuZGF0YSRzY2VfbmFtZVsxXQogIG5fY2VsbHMgPC0gekBtZXRhLmRhdGEkbl9jZWxsc1sxXQogIG5fZ2VuZXMgPC0gekBtZXRhLmRhdGEkbl9nZW5lc1sxXQogIHBlcmNfZGVnIDwtIHpAbWV0YS5kYXRhJHBlcmNfZGVnWzFdCiAgYWxsb2NhdGlvbiA8LSB6QG1ldGEuZGF0YSRhbGxvY2F0aW9uWzFdCiAgbl9zdWJqZWN0cyA8LSBsZW5ndGgodW5pcXVlKHpAbWV0YS5kYXRhJHN1YmplY3QpKQogICMgc3VtbWFyeSBzdGF0IHRhYmxlIAogIHNwYXJzaXR5X2NvdW50IDwtIG1lYW4oekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzID09IDApCiAgbWVhbl9jb3VudCA8LSBtZWFuKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKICBtZWRfY291bnQgPC0gaWZlbHNlKHNwYXJzaXR5X2NvdW50ID4gMC41LCAwLCBtZWRpYW4oekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKSkKICBzZF9jb3VudCA8LSBzZCh6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpCiAgdmFyX2NvdW50IDwtIHNkX2NvdW50XjIgCiAgcmFuZ2VfY291bnQgPC0gcmFuZ2UoekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKQogIHN1bW1hcnlfZGYgPC0gZGF0YS5mcmFtZShtZXRyaWMgPSBjKCJNZWFuIiwgIk1lZGlhbiIsICJTLkQuIiwgIlZhcmlhbmNlIiwgIlJhbmdlIiwgIlNwYXJzaXR5IiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGMocm91bmQobWVhbl9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQobWVkX2NvdW50LCAyKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChzZF9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQodmFyX2NvdW50LCAyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgiKCIsIHJhbmdlX2NvdW50WzFdLCAiLCAiLCByYW5nZV9jb3VudFsyXSwgIikiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAocm91bmQoc3BhcnNpdHlfY291bnQsIDQpICogMTAwLCAiJSIpKSkKICAjIGNyZWF0ZSBjb3VudHMgaGlzdG9ncmFtCiAgcDAgPC0gZGF0YS5mcmFtZSh4ID0gYXMubnVtZXJpYyh6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpKSAlPiUgCiAgICAgICAgZ2dwbG90KGFlcyh4ID0geCkpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJkb2RnZXJibHVlIikgKyAKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9zY2llbnRpZmljKCkpICsgCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfY29tbWEoKSkgKyAKICAgICAgICBsYWJzKHggPSAiUmF3IEV4cHJlc3Npb24iLCB5ID0gIkZyZXF1ZW5jeSIpICsgCiAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkKICAjIGNyZWF0ZSBsb2cgY291bnRzIGhpc3RvZ3JhbQogIHAxIDwtIGRhdGEuZnJhbWUoeCA9IGFzLm51bWVyaWMoekBhc3NheXMkb3JpZ2luYWxleHBAZGF0YSkpICU+JSAKICAgICAgICBnZ3Bsb3QoYWVzKHggPSB4KSkgKyAKICAgICAgICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gImZvcmVzdGdyZWVuIikgKyAKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9zY2llbnRpZmljKCkpICsgCiAgICAgICAgbGFicyh4ID0gIk5vcm1hbGl6ZWQgRXhwcmVzc2lvbiIsIHkgPSAiRnJlcXVlbmN5IikgKyAKICAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KQogICMgY3JlYXRlIFVNQVAgYnkgY2x1c3RlciAmIHN1YmplY3QKICBsZWdlbmRfY2x1c3QgPC0gZ2dwdWJyOjpnZXRfbGVnZW5kKHAgPSAoZGF0YS5mcmFtZShVTUFQMSA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVTUFQMiA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyID0geiRsYWJlbCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3Bsb3QoYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGNsdXN0ZXIpKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9tX3BvaW50KCkgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3NjaTo6Y2F0ZWdvcnkyMF9kMyIpWzE6bGVuZ3RoKHVuaXF1ZSh6JGxhYmVsKSldKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJzKGNvbG9yID0gIkxvdXZhaW5cbkNsdXN0ZXIiKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSArIHRoZW1lKGxlZ2VuZC50ZXh0LmFsaWduID0gMC41KSkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogIGxlZ2VuZF9zdWJqIDwtIGdncHVicjo6Z2V0X2xlZ2VuZChwID0gKGRhdGEuZnJhbWUoVU1BUDEgPSB6QHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3NbLCAxXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVTUFQMiA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YmplY3QgPSB6JHN1YmplY3QpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3Bsb3QoYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IHN1YmplY3QpKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmNhdGVnb3J5MjBfZDMiKVsobGVuZ3RoKHVuaXF1ZSh6JGxhYmVsKSkgKyAxKToyMF0pICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFicyhjb2xvciA9ICJTdWJqZWN0IikgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSkpCiAgcDJhIDwtIGRhdGEuZnJhbWUoVU1BUDEgPSB6QHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3NbLCAxXSwgCiAgICAgICAgICAgICAgICAgICAgVU1BUDIgPSB6QHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3NbLCAyXSwgCiAgICAgICAgICAgICAgICAgICAgY2x1c3RlciA9IHokbGFiZWwsIAogICAgICAgICAgICAgICAgICAgIHN1YmplY3QgPSB6JHN1YmplY3QpICU+JSAKICAgICAgICAgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKGNvbHMgPSBjKGNsdXN0ZXIsIHN1YmplY3QpLCBuYW1lc190byA9ICJpZGVudCIsIHZhbHVlc190byA9ICJpZGVudF92YWx1ZSIpICU+JSAKICAgICAgICAgICBtdXRhdGUoaWRlbnQgPSBjYXNlX3doZW4oaWRlbnQgPT0gImNsdXN0ZXIiIH4gIkxvdXZhaW4gQ2x1c3RlciIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIlN1YmplY3QiKSkgJT4lIAogICAgICAgICAgIGdncGxvdChhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gaWRlbnRfdmFsdWUsIGdyb3VwID0gaWRlbnQpKSArIAogICAgICAgICAgIGZhY2V0X3dyYXAofmlkZW50KSArIAogICAgICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmNhdGVnb3J5MjBfZDMiKSkgKyAKICAgICAgICAgICBsYWJzKHggPSAiVU1BUCAxIiwgeSA9ICJVTUFQIDIiKSArIAogICAgICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCAKICAgICAgICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpCiAgcDJiIDwtIChwMmEgKyAod3JhcF9lbGVtZW50cyhsZWdlbmRfY2x1c3QpIC8gd3JhcF9lbGVtZW50cyhsZWdlbmRfc3ViaikpICsgCiAgICAgICAgIHBsb3RfbGF5b3V0KG5jb2wgPSAyLCB3aWR0aHMgPSBjKDQsIDEpKSkKICAjIGNyZWF0ZSBQQ0Egb2YgY2VsbCBvcmRlcmluZyAKICBwM2EgPC0gZGF0YS5mcmFtZShQQzEgPSB6QHJlZHVjdGlvbnMkUENBQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgICBQQzIgPSB6QHJlZHVjdGlvbnMkUENBQGNlbGwuZW1iZWRkaW5nc1ssIDJdLCAKICAgICAgICAgICAgICAgICAgICBjZWxsX3RpbWUgPSB6JGNlbGxfdGltZV9ub3JtZWQsIAogICAgICAgICAgICAgICAgICAgIHN1YmplY3QgPSB6JHN1YmplY3QpICU+JSAKICAgICAgICAgZ2dwbG90KGFlcyh4ID0gUEMxLCB5ID0gUEMyLCBjb2xvciA9IGNlbGxfdGltZSkpICsgCiAgICAgICAgIGZhY2V0X3dyYXAofnN1YmplY3QpICsgCiAgICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gcGFsZXR0ZWVyX2QoIndlc2FuZGVyc29uOjpaaXNzb3UxIikpICsgCiAgICAgICAgIGxhYnMoeCA9ICJQQyAxIiwgeSA9ICJQQyAyIiwgY29sb3IgPSAiVHJ1ZSBPcmRlcmluZyIpICsgCiAgICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgICAgIHRoZW1lKGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCkpCiAgcDNiIDwtIGRhdGEuZnJhbWUoUEMxID0gekByZWR1Y3Rpb25zJFBDQUBjZWxsLmVtYmVkZGluZ3NbLCAxXSwgCiAgICAgICAgICAgICAgICAgICAgUEMyID0gekByZWR1Y3Rpb25zJFBDQUBjZWxsLmVtYmVkZGluZ3NbLCAyXSwgCiAgICAgICAgICAgICAgICAgICAgY2VsbF90aW1lID0geiRjZWxsX3RpbWVfbm9ybWVkKSAlPiUgCiAgICAgICBnZ3Bsb3QoYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG9yID0gY2VsbF90aW1lKSkgKyAKICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHBhbGV0dGVlcl9kKCJ3ZXNhbmRlcnNvbjo6Wmlzc291MSIpKSArIAogICAgICAgbGFicyh4ID0gIlBDIDEiLCB5ID0gIlBDIDIiLCBjb2xvciA9ICJUcnVlIE9yZGVyaW5nIikgKyAKICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgICB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSkKICBwM2MgPC0gKHAzYSB8IHAzYikgKyAKICAgICAgICAgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiLCB3aWR0aHMgPSBjKDMsIDIpLCBuY29sID0gMikKICAjIHRhYmxlIG9mIHNpbXVsYXRpb24gcGFyYW1ldGVycyAKICBwYXJhbV9kZiA8LSBkYXRhLmZyYW1lKG1ldHJpYyA9IGMoIk4gQ2VsbHMiLCAiTiBHZW5lcyIsICIlIER5bmFtaWMiLCAiQWxsb2NhdGlvbiIsICJOIFN1YmplY3RzIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBjKGFzLmNoYXJhY3RlcihuX2NlbGxzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKG5fZ2VuZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfZGVnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGxvY2F0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3N1YmplY3RzKSkKICBwbG90X3RhYmxlIDwtIHJiaW5kKHN1bW1hcnlfZGYsIHBhcmFtX2RmKSAlPiUgCiAgICAgICAgICAgICAgICBncmlkRXh0cmE6OnRhYmxlR3JvYihyb3dzID0gTlVMTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xzID0gYygiTWV0cmljIiwgIlZhbHVlIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUgPSBncmlkRXh0cmE6OnR0aGVtZV9taW5pbWFsKGNvcmUgPSBsaXN0KGZnX3BhcmFtcyA9IGxpc3QoaGp1c3QgPSAwLCB4ID0gMC4wNSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xoZWFkID0gbGlzdChmZ19wYXJhbXMgPSBsaXN0KGhqdXN0ID0gMCwgeCA9IDAuMDUpKSkpICU+JSAKICAgICAgICAgICAgICAgIGd0YWJsZTo6Z3RhYmxlX2FkZF9ncm9iKGdyb2JzID0gZ3JpZDo6cmVjdEdyb2IoZ3AgPSBncmlkOjpncGFyKGZpbGwgPSBOQSwgbHdkID0gMikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHQgPSAyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGIgPSBucm93KC4pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGwgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHIgPSBuY29sKC4pKSAlPiUgCiAgICAgICAgICAgICAgICBndGFibGU6Omd0YWJsZV9hZGRfZ3JvYihncm9icyA9IGdyaWQ6OnJlY3RHcm9iKGdwID0gZ3JpZDo6Z3BhcihmaWxsID0gTkEsIGx3ZCA9IDIpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByID0gbmNvbCguKSkKICAjIGFsaWduIGV2ZXJ5dGhpbmcgCiAgcDRhIDwtICgocDAgfCBwMSB8IHBsb3RfdGFibGUpICsgcGxvdF9sYXlvdXQod2lkdGhzID0gYygxLCAxLCAwLjUpKSkgLyAocDJiIHwgcDNjKSArIAogICAgICAgICBwbG90X2xheW91dChucm93ID0gMikgKyAKICAgICAgICAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gcGFzdGUwKCJNZXRyaWNzIGZvciBkYXRhc2V0OiAiLCBvYmpfbmFtZSkpCiAgIyBzYXZlICYgcHJpbnQgcGxvdAogIGdnc2F2ZShmaWxlbmFtZSA9IHBhc3RlMCgiUUNfIiwgb2JqX25hbWUsICIucGRmIiksCiAgICAgICAgIHBsb3QgPSBwNGIsIAogICAgICAgICBkZXZpY2UgPSAicGRmIiwgCiAgICAgICAgIHBhdGggPSAiL2JsdWUvcmJhY2hlci9qLmxlYXJ5L3JlcG9zL3NjTEFORV9BbmFseXNpcy9GaWd1cmVzL1FDX1Bsb3RzLyIsIAogICAgICAgICB3aWR0aCA9IDEzLAogICAgICAgICBoZWlnaHQgPSA5LCAKICAgICAgICAgdW5pdHMgPSAiaW4iLCAKICAgICAgICAgZHBpID0gInJldGluYSIpCiAgcHJpbnQocDRhKQogICMgY2xlYW51cCAKICBzaW5rKHRlbXBmaWxlKCkpCiAgcm0ocDAsIHAxLCBwMmEsIHAyYiwgcDNhLCBwM2IsIHAzYywgcDRhLCBwbG90X3RhYmxlKTsgZ2MoZnVsbCA9IFRSVUUpCiAgc2luaygpCn0pCnJtKG9ial9saXN0KQpgYGAKCiMgUGFuY3JlYXRpYyBFbmRvY3Jpbm9nZW5lc2lzIFJlZmVyZW5jZSAKCkZpcnN0IGxldCdzIHRha2UgYSBsb29rIGF0IHRoZSByZWZlcmVuY2UgZGF0YXNldCBpdHNlbGYuIExvYWRpbmcgdGhpcyBvbmUgaXMgYSBiaXQgbW9yZSBjb21wbGljYXRlZCAtIGl0IGNvbWVzIGFzIHBhcnQgb2YgdGhlIGBzY1ZlbG9gIFB5dGhvbiBsaWJyYXJ5LCBzbyB3ZSB1c2UgdGhlIGB7cmV0aWN1bGF0ZX1gIFIgcGFja2FnZSB0byBhY2Nlc3MgJiBsb2FkIGl0IGJlZm9yZSBjb252ZXJ0aW5nIGl0IHRvIGEgYFNpbmdsZUNlbGxFeHBlcmltZW50YCBvYmplY3QgdXNpbmcgdGhlIGB7emVsbGtvbnZlcnRlcn1gIHBhY2thZ2UuIFRob3VnaCB0aGUgZGF0YSBjb21lcyBwcmUtcHJvY2Vzc2VkLCB3ZSByZXByb2Nlc3MgaXQgdXNpbmcgdGhlIHNhbWUgc3RlcHMgYXMgd2UgZGlkIG9uIHRoZSBvdGhlciB0d28gZGF0YXNldHMgdG8gZW5zdXJlIGEgZ29vZCBjb21wYXJpc29uLiBBIG5lY2Vzc2FyeSB3cmlua2xlIGhlcmUgaXMgdGhlIGFjdGl2YXRpb24gb2YgYSBgY29uZGFgIGVudmlyb25tZW50IHRoYXQgSSBjcmVhdGVkIHByZXZpb3VzbHkgJiBpbnN0YWxsZWQgYHNjVmVsb2AgYW5kIGl0cyBkZXBlbmRlbmNpZXMgaW50by4gCgpgYGB7cn0KcmV0aWN1bGF0ZTo6dXNlX2NvbmRhZW52KGNvbmRhZW52ID0gIkhQR192ZW52IiwKICAgICAgICAgICAgICAgICAgICAgICAgIGNvbmRhID0gIi9hcHBzL2NvbmRhLzIyLjExLjEvY29uZGFiaW4vY29uZGEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHJlcXVpcmVkID0gVFJVRSkKc2N2ZWxvIDwtIHJldGljdWxhdGU6OmltcG9ydCgic2N2ZWxvIikKYWRhdGEgPC0gc2N2ZWxvJGRhdGFzZXRzJHBhbmNyZWFzKCkKZW5kbyA8LSB6ZWxsa29udmVydGVyOjpBbm5EYXRhMlNDRShhZGF0YSA9IGFkYXRhKQplbmRvQGFzc2F5c0BkYXRhJFggPC0gTlVMTAplbmRvQGFzc2F5c0BkYXRhJGNvdW50cyA8LSBlbmRvQGFzc2F5c0BkYXRhJHNwbGljZWQKZW5kb19kYXRhX2NsZWFuIDwtIGVuZG9bcm93U3VtcyhTaW5nbGVDZWxsRXhwZXJpbWVudDo6Y291bnRzKGVuZG8pID4gMCkgPj0gMywgXQplbmRvX2RhdGFfY2xlYW4gPC0gbG9nTm9ybUNvdW50cyhlbmRvX2RhdGFfY2xlYW4pCnZhcl9kZWNvbXAgPC0gbW9kZWxHZW5lVmFyKGVuZG9fZGF0YV9jbGVhbikKdG9wMmtfaHZncyA8LSBnZXRUb3BIVkdzKHZhcl9kZWNvbXAsIG4gPSAyMDAwKQplbmRvX2RhdGFfY2xlYW4gPC0gcnVuUENBKGVuZG9fZGF0YV9jbGVhbiwgc3Vic2V0X3JvdyA9IHRvcDJrX2h2Z3MpCnJlZHVjZWREaW0oZW5kb19kYXRhX2NsZWFuLCAiUENBc3ViIikgPC0gcmVkdWNlZERpbShlbmRvX2RhdGFfY2xlYW4sICJQQ0EiKVssIDE6MzAsIGRyb3AgPSBGQUxTRV0KZW5kb19kYXRhX2NsZWFuIDwtIHJ1blVNQVAoZW5kb19kYXRhX2NsZWFuLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gIlBDQXN1YiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX2RpbXJlZCA9IDE6MzApCmcgPC0gYnVpbGRTTk5HcmFwaChlbmRvX2RhdGFfY2xlYW4sCiAgICAgICAgICAgICAgICAgICB1c2UuZGltcmVkID0gIlBDQXN1YiIsCiAgICAgICAgICAgICAgICAgICBrID0gMzApCmNsdXN0ZXJzIDwtIGlncmFwaDo6Y2x1c3Rlcl9sb3V2YWluKGdyYXBoID0gZykkbWVtYmVyc2hpcApjb2xMYWJlbHMoZW5kb19kYXRhX2NsZWFuKSA8LSBmYWN0b3IoY2x1c3RlcnMpCmVuZG9fZGF0YV9jbGVhbiA8LSBhcy5TZXVyYXQoZW5kb19kYXRhX2NsZWFuLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3VudHMgPSAiY291bnRzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9ICJsb2djb3VudHMiKQpgYGAKCldlIHJlLWNyZWF0ZSB0aGUgc3VtbWFyeSBzdGF0aXN0aWNzIHRhYmxlIGZvciB0aGUgcGFuY3JlYXMgcmVmZXJlbmNlLiAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30Kbl9jZWxscyA8LSBuY29sKGVuZG9fZGF0YV9jbGVhbikKbl9nZW5lcyA8LSBucm93KGVuZG9fZGF0YV9jbGVhbikKbWVhbl9jb3VudCA8LSBtZWFuKGVuZG9fZGF0YV9jbGVhbkBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKQptZWRfY291bnQgPC0gMApzZF9jb3VudCA8LSBzZChlbmRvX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKdmFyX2NvdW50IDwtIHNkX2NvdW50XjIgICMgZmFzdGVyIApyYW5nZV9jb3VudCA8LSByYW5nZShlbmRvX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKc3BhcnNpdHlfY291bnQgPC0gbWVhbihlbmRvX2RhdGFfY2xlYW5AYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cyA9PSAwKQpzdW1tYXJ5X2RmIDwtIGRhdGEuZnJhbWUobWV0cmljID0gYygiTWVhbiIsICJNZWRpYW4iLCAiUy5ELiIsICJWYXJpYW5jZSIsICJSYW5nZSIsICJTcGFyc2l0eSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gYyhyb3VuZChtZWFuX2NvdW50LCAyKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVkX2NvdW50LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChzZF9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKHZhcl9jb3VudCwgMiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCIoIiwgcmFuZ2VfY291bnRbMV0sICIsICIsIHJhbmdlX2NvdW50WzJdLCAiKSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAocm91bmQoc3BhcnNpdHlfY291bnQsIDQpICogMTAwLCAiJSIpKSkKcGxvdF90YWJsZSA8LSBncmlkRXh0cmE6OnRhYmxlR3JvYihzdW1tYXJ5X2RmLCByb3dzID0gTlVMTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29scyA9IGMoIk1ldHJpYyIsICJWYWx1ZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZSA9IGdyaWRFeHRyYTo6dHRoZW1lX21pbmltYWwoY29yZSA9IGxpc3QoZmdfcGFyYW1zID0gbGlzdChoanVzdCA9IDAsIHggPSAwLjA1KSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xoZWFkID0gbGlzdChmZ19wYXJhbXMgPSBsaXN0KGhqdXN0ID0gMCwgeCA9IDAuMDUpKSkpICU+JSAKICAgICAgICAgICAgICBndGFibGU6Omd0YWJsZV9hZGRfZ3JvYihncm9icyA9IGdyaWQ6OnJlY3RHcm9iKGdwID0gZ3JpZDo6Z3BhcihmaWxsID0gTkEsIGx3ZCA9IDIpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdCA9IDIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGIgPSBucm93KC4pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgciA9IG5jb2woLikpICU+JSAKICAgICAgICAgICAgICBndGFibGU6Omd0YWJsZV9hZGRfZ3JvYihncm9icyA9IGdyaWQ6OnJlY3RHcm9iKGdwID0gZ3JpZDo6Z3BhcihmaWxsID0gTkEsIGx3ZCA9IDIpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGwgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByID0gbmNvbCguKSkKZ2MoZnVsbCA9IFRSVUUpCmBgYAoKTGlrZSB0aGUgcGFuY3JlYXMgcmVmZXJlbmNlLCB0aGlzIGRhdGFzZXQgaXMgc21hbGwgZW5vdWdoIHRoYXQgd2UgZG9uJ3QgbmVlZCB0byBkbyBhbnkgdHJpY2tlcnkgdG8gcHJvZHVjZSBoaXN0b2dyYW1zLiAKCmBgYHtyfQpwMCA8LSBkYXRhLmZyYW1lKHggPSBhcy5udW1lcmljKGVuZG9fZGF0YV9jbGVhbkBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKSkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHggPSB4KSkgKyAKICAgICAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJkb2RnZXJibHVlIikgKyAKICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfc2NpZW50aWZpYygpKSArIAogICAgICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9jb21tYSgpKSArIAogICAgICBsYWJzKHggPSAiUmF3IEV4cHJlc3Npb24iLCB5ID0gIkZyZXF1ZW5jeSIpICsgCiAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpCnAxIDwtIGRhdGEuZnJhbWUoeCA9IGFzLm51bWVyaWMoZW5kb19kYXRhX2NsZWFuQGFzc2F5cyRvcmlnaW5hbGV4cEBkYXRhKSkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHggPSB4KSkgKyAKICAgICAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJmb3Jlc3RncmVlbiIpICsgCiAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX3NjaWVudGlmaWMoKSkgKyAKICAgICAgbGFicyh4ID0gIk5vcm1hbGl6ZWQgRXhwcmVzc2lvbiIsIHkgPSAiRnJlcXVlbmN5IikgKyAKICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkKYGBgCgpUaGUgVU1BUCAmIFBDQSBwbG90cyBhcmUgZ2VuZXJhdGVkIGFzIGJlZm9yZS4gCgpgYGB7cn0KcDIgPC0gZGF0YS5mcmFtZShVTUFQMSA9IGVuZG9fZGF0YV9jbGVhbkByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzWywgMV0sIAogICAgICAgICAgICAgICAgIFVNQVAyID0gZW5kb19kYXRhX2NsZWFuQHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3NbLCAyXSwgCiAgICAgICAgICAgICAgICAgY2x1c3RlciA9IGVuZG9fZGF0YV9jbGVhbiRsYWJlbCkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGNsdXN0ZXIpKSArIAogICAgICBnZW9tX3BvaW50KCkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3NjaTo6Y2F0ZWdvcnkyMF9kMyIpKSArIAogICAgICBsYWJzKHggPSAiVU1BUCAxIiwgeSA9ICJVTUFQIDIiLCBjb2xvciA9ICJMb3V2YWluIENsdXN0ZXIiKSArIAogICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSArIAogICAgICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSArCiAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyKSkpCnAzIDwtIGRhdGEuZnJhbWUoUEMxID0gZW5kb19kYXRhX2NsZWFuQHJlZHVjdGlvbnMkUENBQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICBQQzIgPSBlbmRvX2RhdGFfY2xlYW5AcmVkdWN0aW9ucyRQQ0FAY2VsbC5lbWJlZGRpbmdzWywgMl0sIAogICAgICAgICAgICAgICAgIGNsdXN0ZXIgPSBlbmRvX2RhdGFfY2xlYW4kbGFiZWwpICU+JSAKICAgICAgZ2dwbG90KGFlcyh4ID0gUEMxLCB5ID0gUEMyLCBjb2xvciA9IGNsdXN0ZXIpKSArIAogICAgICBnZW9tX3BvaW50KCkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3NjaTo6Y2F0ZWdvcnkyMF9kMyIpKSArIAogICAgICBsYWJzKHggPSAiUEMgMSIsIHkgPSAiUEMgMiIsIGNvbG9yID0gIkxvdXZhaW4gQ2x1c3RlciIpICsgCiAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgCiAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpCnAzCmBgYAoKV2Ugc2F2ZSB0aGUgZmluYWwgcGxvdCBhcyBhIFBERiAmIGRpc3BsYXkgaXQuIAoKYGBge3IsIGZpZy53aWR0aD0xMywgZmlnLmhlaWdodD04fQpwNGEgPC0gKHAwIHwgcDEpIC8gKHAyIHwgcDMpICsgCiAgICAgICBwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIpCnA0YiA8LSAocDRhIHwgcGxvdF90YWJsZSkgKyAKICAgICAgIHBsb3RfbGF5b3V0KG5jb2wgPSAyLCB3aWR0aHMgPSBjKDMsIDEpKSArIAogICAgICAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gcGFzdGUwKCJNZXRyaWNzIGZvciBFbmRvY3Jpbm9nZW5lc2lzIFJlZmVyZW5jZSBEYXRhc2V0IikpCmdnc2F2ZShmaWxlbmFtZSA9ICJRQ19lbmRvX3JlZmVyZW5jZS5wZGYiLAogICAgICAgcGxvdCA9IHA0YiwgCiAgICAgICBkZXZpY2UgPSAicGRmIiwgCiAgICAgICBwYXRoID0gIi9ibHVlL3JiYWNoZXIvai5sZWFyeS9yZXBvcy9zY0xBTkVfQW5hbHlzaXMvRmlndXJlcy9RQ19QbG90cy8iLCAKICAgICAgIHdpZHRoID0gMTMsCiAgICAgICBoZWlnaHQgPSA4LCAKICAgICAgIHVuaXRzID0gImluIiwgCiAgICAgICBkcGkgPSAicmV0aW5hIikKcDRiCmBgYAoKIyMgU2luZ2xlLXN1YmplY3QgCgpXZSBsb2FkIHRoZSBzaW5nbGUtc3ViamVjdCBlbmRvY3Jpbm9nZW5lc2lzIGRhdGFzZXRzLiAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KIyAxMDAgY2VsbHMgCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18wMV9DRUxMU18xMDApCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18wNV9DRUxMU18xMDApCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18xMF9DRUxMU18xMDApCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18yMF9DRUxMU18xMDApCiMgNTAwIGNlbGxzIAp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMDFfQ0VMTFNfNTAwKQp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMDVfQ0VMTFNfNTAwKQp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMTBfQ0VMTFNfNTAwKQp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMjBfQ0VMTFNfNTAwKQojIDEsMDAwIGNlbGxzIAp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMDFfQ0VMTFNfMTAwMCkKdGFyX2xvYWQoZW5kb19zaW1fREVHXzA1X0NFTExTXzEwMDApCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18xMF9DRUxMU18xMDAwKQp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMjBfQ0VMTFNfMTAwMCkKIyAyLDUwMCBjZWxscwp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMDFfQ0VMTFNfMjUwMCkKdGFyX2xvYWQoZW5kb19zaW1fREVHXzA1X0NFTExTXzI1MDApCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18xMF9DRUxMU18yNTAwKQp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMjBfQ0VMTFNfMjUwMCkKIyA1LDAwMCBjZWxscyAKdGFyX2xvYWQoZW5kb19zaW1fREVHXzAxX0NFTExTXzUwMDApCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18wNV9DRUxMU181MDAwKQp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMTBfQ0VMTFNfNTAwMCkKdGFyX2xvYWQoZW5kb19zaW1fREVHXzIwX0NFTExTXzUwMDApCiMgY29lcmNlIHRvIGxpc3QgJiBwcm9jZXNzCm9ial9saXN0IDwtIHB1cnJyOjptYXAobHMocGF0dGVybiA9ICJlbmRvX3NpbSIpWyFncmVwbCgiYmFsYW5jZWQiLCBscyhwYXR0ZXJuID0gImVuZG9fc2ltIikpXSwgZnVuY3Rpb24oc2ltKSB7CiAgb2JqIDwtIGV2YWwoYXMuc3ltYm9sKHNpbSkpCiAgb2JqIDwtIGFzLlNldXJhdChvYmosIGNvdW50cyA9ICJjb3VudHMiLCBkYXRhID0gImxvZ2NvdW50cyIpCiAgb2JqQG1ldGEuZGF0YSA8LSBtdXRhdGUob2JqQG1ldGEuZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGVyY19kZWcgPSBwYXN0ZTAoYXMubnVtZXJpYyhzdHJpbmdyOjpzdHJfcmVtb3ZlKHN0cmluZ3I6OnN0cl9leHRyYWN0KHNpbSwgImVuZG9fc2ltX0RFR18uLiIpLCAiZW5kb19zaW1fREVHXyIpKSwgIiUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jZWxscyA9IGFzLmNoYXJhY3RlcihuY29sKG9iaikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBuX2dlbmVzID0gYXMuY2hhcmFjdGVyKG5yb3cob2JqKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgIHNjZV9uYW1lID0gc2ltKQogIHJldHVybihvYmopCn0pCnJtKGxpc3QgPSBscyhwYXR0ZXJuID0gImVuZG9fc2ltIikpOyBnYyhmdWxsID0gVFJVRSkKYGBgCgpXZSBnZW5lcmF0ZSB0aGUgcGxvdHMgZm9yIGVhY2ggZGF0YXNldC4gCgpgYGB7ciwgZmlnLndpZHRoPTEzLCBmaWcuaGVpZ2h0PTgsIHJlc3VsdHM9J2hvbGQnfQpwdXJycjo6d2FsayhvYmpfbGlzdCwgZnVuY3Rpb24oeikgewogICMgZ2F0aGVyIG1ldGFkYXRhIAogIG9ial9uYW1lIDwtIHpAbWV0YS5kYXRhJHNjZV9uYW1lWzFdCiAgbl9jZWxscyA8LSB6QG1ldGEuZGF0YSRuX2NlbGxzWzFdCiAgbl9nZW5lcyA8LSB6QG1ldGEuZGF0YSRuX2dlbmVzWzFdCiAgcGVyY19kZWcgPC0gekBtZXRhLmRhdGEkcGVyY19kZWdbMV0KICAjIHN1bW1hcnkgc3RhdCB0YWJsZSAKICBzcGFyc2l0eV9jb3VudCA8LSBtZWFuKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cyA9PSAwKQogIG1lYW5fY291bnQgPC0gbWVhbih6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpCiAgbWVkX2NvdW50IDwtIGlmZWxzZShzcGFyc2l0eV9jb3VudCA+IDAuNSwgMCwgbWVkaWFuKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykpCiAgc2RfY291bnQgPC0gc2QoekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKQogIHZhcl9jb3VudCA8LSBzZF9jb3VudF4yIAogIHJhbmdlX2NvdW50IDwtIHJhbmdlKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKICBzdW1tYXJ5X2RmIDwtIGRhdGEuZnJhbWUobWV0cmljID0gYygiTWVhbiIsICJNZWRpYW4iLCAiUy5ELiIsICJWYXJpYW5jZSIsICJSYW5nZSIsICJTcGFyc2l0eSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBjKHJvdW5kKG1lYW5fY291bnQsIDIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKG1lZF9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoc2RfY291bnQsIDIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKHZhcl9jb3VudCwgMiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoIigiLCByYW5nZV9jb3VudFsxXSwgIiwgIiwgcmFuZ2VfY291bnRbMl0sICIpIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKHJvdW5kKHNwYXJzaXR5X2NvdW50LCA0KSAqIDEwMCwgIiUiKSkpCiAgIyBjcmVhdGUgY291bnRzIGhpc3RvZ3JhbQogIHAwIDwtIGRhdGEuZnJhbWUoeCA9IGFzLm51bWVyaWMoekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKSkgJT4lIAogICAgICAgIGdncGxvdChhZXMoeCA9IHgpKSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGZpbGwgPSAiZG9kZ2VyYmx1ZSIpICsgCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfc2NpZW50aWZpYygpKSArIAogICAgICAgIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX2NvbW1hKCkpICsgCiAgICAgICAgbGFicyh4ID0gIlJhdyBFeHByZXNzaW9uIiwgeSA9ICJGcmVxdWVuY3kiKSArIAogICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpCiAgIyBjcmVhdGUgbG9nIGNvdW50cyBoaXN0b2dyYW0KICBwMSA8LSBkYXRhLmZyYW1lKHggPSBhcy5udW1lcmljKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGRhdGEpKSAlPiUgCiAgICAgICAgZ2dwbG90KGFlcyh4ID0geCkpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJmb3Jlc3RncmVlbiIpICsgCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfc2NpZW50aWZpYygpKSArIAogICAgICAgIGxhYnMoeCA9ICJOb3JtYWxpemVkIEV4cHJlc3Npb24iLCB5ID0gIkZyZXF1ZW5jeSIpICsgCiAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkKICAjIGNyZWF0ZSBVTUFQIGJ5IGNsdXN0ZXIgCiAgcDIgPC0gZGF0YS5mcmFtZShVTUFQMSA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgIFVNQVAyID0gekByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzWywgMl0sIAogICAgICAgICAgICAgICAgICAgY2x1c3RlciA9IHokbGFiZWwpICU+JSAKICAgICAgICBnZ3Bsb3QoYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGNsdXN0ZXIpKSArIAogICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmNhdGVnb3J5MjBfZDMiKSkgKyAKICAgICAgICBsYWJzKHggPSAiVU1BUCAxIiwgeSA9ICJVTUFQIDIiLCBjb2xvciA9ICJMb3V2YWluIENsdXN0ZXIiKSArIAogICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgICAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSArIAogICAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyKSkpCiAgIyBjcmVhdGUgUENBIG9mIGNlbGwgb3JkZXJpbmcgCiAgcDMgPC0gZGF0YS5mcmFtZShQQzEgPSB6QHJlZHVjdGlvbnMkUENBQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgIFBDMiA9IHpAcmVkdWN0aW9ucyRQQ0FAY2VsbC5lbWJlZGRpbmdzWywgMl0sIAogICAgICAgICAgICAgICAgICAgY2VsbF90aW1lID0geiRjZWxsX3RpbWVfbm9ybWVkKSAlPiUgCiAgICAgICAgZ2dwbG90KGFlcyh4ID0gUEMxLCB5ID0gUEMyLCBjb2xvciA9IGNlbGxfdGltZSkpICsgCiAgICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHBhbGV0dGVlcl9kKCJ3ZXNhbmRlcnNvbjo6Wmlzc291MSIpKSArIAogICAgICAgIGxhYnMoeCA9ICJQQyAxIiwgeSA9ICJQQyAyIiwgY29sb3IgPSAiVHJ1ZSBPcmRlcmluZyIpICsgCiAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkgKyAKICAgICAgICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpCiAgIyB0YWJsZSBvZiBzaW11bGF0aW9uIHBhcmFtZXRlcnMgCiAgcGFyYW1fZGYgPC0gZGF0YS5mcmFtZShtZXRyaWMgPSBjKCJOdW1iZXIgb2YgQ2VsbHMiLCAiTnVtYmVyIG9mIEdlbmVzIiwgIiUgRHluYW1pYyBHZW5lcyIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gYyhhcy5jaGFyYWN0ZXIobl9jZWxscyksIGFzLmNoYXJhY3RlcihuX2dlbmVzKSwgcGVyY19kZWcpKQogIHBsb3RfdGFibGUgPC0gcmJpbmQoc3VtbWFyeV9kZiwgcGFyYW1fZGYpICU+JSAKICAgICAgICAgICAgICAgIGdyaWRFeHRyYTo6dGFibGVHcm9iKHJvd3MgPSBOVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSBjKCJNZXRyaWMiLCAiVmFsdWUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZSA9IGdyaWRFeHRyYTo6dHRoZW1lX21pbmltYWwoY29yZSA9IGxpc3QoZmdfcGFyYW1zID0gbGlzdChoanVzdCA9IDAsIHggPSAwLjA1KSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGhlYWQgPSBsaXN0KGZnX3BhcmFtcyA9IGxpc3QoaGp1c3QgPSAwLCB4ID0gMC4wNSkpKSkgJT4lIAogICAgICAgICAgICAgICAgZ3RhYmxlOjpndGFibGVfYWRkX2dyb2IoZ3JvYnMgPSBncmlkOjpyZWN0R3JvYihncCA9IGdyaWQ6OmdwYXIoZmlsbCA9IE5BLCBsd2QgPSAyKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdCA9IDIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYiA9IG5yb3coLiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgciA9IG5jb2woLikpICU+JSAKICAgICAgICAgICAgICAgIGd0YWJsZTo6Z3RhYmxlX2FkZF9ncm9iKGdyb2JzID0gZ3JpZDo6cmVjdEdyb2IoZ3AgPSBncmlkOjpncGFyKGZpbGwgPSBOQSwgbHdkID0gMikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHQgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGwgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHIgPSBuY29sKC4pKQogICMgYWxpZ24gZXZlcnl0aGluZyAKICBwNGEgPC0gKHAwIHwgcDEpIC8gKHAyIHwgcDMpICsgCiAgICAgICAgIHBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikKICBwNGIgPC0gKHA0YSB8IHBsb3RfdGFibGUpICsgCiAgICAgICAgIHBsb3RfbGF5b3V0KG5jb2wgPSAyLCB3aWR0aHMgPSBjKDMsIDEpKSArIAogICAgICAgICBwbG90X2Fubm90YXRpb24odGl0bGUgPSBwYXN0ZTAoIk1ldHJpY3MgZm9yIGRhdGFzZXQ6ICIsIG9ial9uYW1lKSkKICAjIHNhdmUgJiBwcmludCBwbG90CiAgZ2dzYXZlKGZpbGVuYW1lID0gcGFzdGUwKCJRQ18iLCBvYmpfbmFtZSwgIi5wZGYiKSwKICAgICAgICAgcGxvdCA9IHA0YiwgCiAgICAgICAgIGRldmljZSA9ICJwZGYiLCAKICAgICAgICAgcGF0aCA9ICIvYmx1ZS9yYmFjaGVyL2oubGVhcnkvcmVwb3Mvc2NMQU5FX0FuYWx5c2lzL0ZpZ3VyZXMvUUNfUGxvdHMvIiwgCiAgICAgICAgIHdpZHRoID0gMTMsCiAgICAgICAgIGhlaWdodCA9IDgsIAogICAgICAgICB1bml0cyA9ICJpbiIsIAogICAgICAgICBkcGkgPSAicmV0aW5hIikKICBwcmludChwNGIpCiAgIyBjbGVhbnVwIAogIHNpbmsodGVtcGZpbGUoKSkKICBybShwMCwgcDEsIHAyLCBwMywgcDRhLCBwNGIsIHBsb3RfdGFibGUpOyBnYyhmdWxsID0gVFJVRSkKICBzaW5rKCkKfSkKcm0ob2JqX2xpc3QpCmBgYAoKIyMgTXVsdGktc3ViamVjdCAKCkxhc3RseSwgd2UgYnJpbmcgdGhlIG11bHRpLXN1YmplY3Qgc2ltdWxhdGVkIGRhdGFzZXRzIGludG8gbWVtb3J5LiAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KIyAxMDAgY2VsbHMKdGFyX2xvYWQoZW5kb19zaW1fREVHXzEwX0NFTExTXzEwMF9iYWxhbmNlZCkKdGFyX2xvYWQoZW5kb19zaW1fREVHXzIwX0NFTExTXzEwMF9iYWxhbmNlZCkKdGFyX2xvYWQoZW5kb19zaW1fREVHXzEwX0NFTExTXzEwMF91bmJhbGFuY2VkKQp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMjBfQ0VMTFNfMTAwX3VuYmFsYW5jZWQpCiMgNTAwIGNlbGxzCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18xMF9DRUxMU181MDBfYmFsYW5jZWQpCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18yMF9DRUxMU181MDBfYmFsYW5jZWQpCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18xMF9DRUxMU181MDBfdW5iYWxhbmNlZCkKdGFyX2xvYWQoZW5kb19zaW1fREVHXzIwX0NFTExTXzUwMF91bmJhbGFuY2VkKQojIDEsMDAwIGNlbGxzIAp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMTBfQ0VMTFNfMTAwMF9iYWxhbmNlZCkKdGFyX2xvYWQoZW5kb19zaW1fREVHXzIwX0NFTExTXzEwMDBfYmFsYW5jZWQpCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18xMF9DRUxMU18xMDAwX3VuYmFsYW5jZWQpCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18yMF9DRUxMU18xMDAwX3VuYmFsYW5jZWQpCiMgMiw1MDAgY2VsbHMKdGFyX2xvYWQoZW5kb19zaW1fREVHXzEwX0NFTExTXzI1MDBfYmFsYW5jZWQpCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18yMF9DRUxMU18yNTAwX2JhbGFuY2VkKQp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMTBfQ0VMTFNfMjUwMF91bmJhbGFuY2VkKQp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMjBfQ0VMTFNfMjUwMF91bmJhbGFuY2VkKQojIDUsMDAwIGNlbGxzIAp0YXJfbG9hZChlbmRvX3NpbV9ERUdfMTBfQ0VMTFNfNTAwMF9iYWxhbmNlZCkKdGFyX2xvYWQoZW5kb19zaW1fREVHXzIwX0NFTExTXzUwMDBfYmFsYW5jZWQpCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18xMF9DRUxMU181MDAwX3VuYmFsYW5jZWQpCnRhcl9sb2FkKGVuZG9fc2ltX0RFR18yMF9DRUxMU181MDAwX3VuYmFsYW5jZWQpCiMgY29lcmNlIHRvIGxpc3QgJiBwcm9jZXNzCm9ial9saXN0IDwtIHB1cnJyOjptYXAobHMocGF0dGVybiA9ICJlbmRvX3NpbSpiYWxhbmNlZCIpLCBmdW5jdGlvbihzaW0pIHsKICBvYmogPC0gZXZhbChhcy5zeW1ib2woc2ltKSkKICBvYmogPC0gYXMuU2V1cmF0KG9iaiwgY291bnRzID0gImNvdW50cyIsIGRhdGEgPSAibG9nY291bnRzIikKICBvYmpAbWV0YS5kYXRhIDwtIG11dGF0ZShvYmpAbWV0YS5kYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBwZXJjX2RlZyA9IHBhc3RlMChhcy5udW1lcmljKHN0cmluZ3I6OnN0cl9yZW1vdmUoc3RyaW5ncjo6c3RyX2V4dHJhY3Qoc2ltLCAiZW5kb19zaW1fREVHXy4uIiksICJlbmRvX3NpbV9ERUdfIikpLCAiJSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBuX2NlbGxzID0gYXMuY2hhcmFjdGVyKG5jb2wob2JqKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgIG5fZ2VuZXMgPSBhcy5jaGFyYWN0ZXIobnJvdyhvYmopKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWxsb2NhdGlvbiA9IGlmZWxzZShncmVwbCgiX2JhbGFuY2VkIiwgc2ltKSwgIkJhbGFuY2VkIiwgIlVuYmFsYW5jZWQiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2NlX25hbWUgPSBzaW0pCiAgcmV0dXJuKG9iaikKfSkKcm0obGlzdCA9IGxzKHBhdHRlcm4gPSAiZW5kb19zaW0iKSk7IGdjKGZ1bGwgPSBUUlVFKQpgYGAKCldlIGdlbmVyYXRlIHRoZSBmaW5hbCBzZXQgb2YgcGxvdHMuIAoKYGBge3IsIGZpZy53aWR0aD0xMywgZmlnLmhlaWdodD05LCByZXN1bHRzPSdob2xkJ30KcHVycnI6OndhbGsob2JqX2xpc3QsIGZ1bmN0aW9uKHopIHsKICAjIGdhdGhlciBtZXRhZGF0YSAKICBvYmpfbmFtZSA8LSB6QG1ldGEuZGF0YSRzY2VfbmFtZVsxXQogIG5fY2VsbHMgPC0gekBtZXRhLmRhdGEkbl9jZWxsc1sxXQogIG5fZ2VuZXMgPC0gekBtZXRhLmRhdGEkbl9nZW5lc1sxXQogIHBlcmNfZGVnIDwtIHpAbWV0YS5kYXRhJHBlcmNfZGVnWzFdCiAgYWxsb2NhdGlvbiA8LSB6QG1ldGEuZGF0YSRhbGxvY2F0aW9uWzFdCiAgbl9zdWJqZWN0cyA8LSBsZW5ndGgodW5pcXVlKHpAbWV0YS5kYXRhJHN1YmplY3QpKQogICMgc3VtbWFyeSBzdGF0IHRhYmxlIAogIHNwYXJzaXR5X2NvdW50IDwtIG1lYW4oekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzID09IDApCiAgbWVhbl9jb3VudCA8LSBtZWFuKHpAYXNzYXlzJG9yaWdpbmFsZXhwQGNvdW50cykKICBtZWRfY291bnQgPC0gaWZlbHNlKHNwYXJzaXR5X2NvdW50ID4gMC41LCAwLCBtZWRpYW4oekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKSkKICBzZF9jb3VudCA8LSBzZCh6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpCiAgdmFyX2NvdW50IDwtIHNkX2NvdW50XjIgCiAgcmFuZ2VfY291bnQgPC0gcmFuZ2UoekBhc3NheXMkb3JpZ2luYWxleHBAY291bnRzKQogIHN1bW1hcnlfZGYgPC0gZGF0YS5mcmFtZShtZXRyaWMgPSBjKCJNZWFuIiwgIk1lZGlhbiIsICJTLkQuIiwgIlZhcmlhbmNlIiwgIlJhbmdlIiwgIlNwYXJzaXR5IiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGMocm91bmQobWVhbl9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQobWVkX2NvdW50LCAyKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChzZF9jb3VudCwgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQodmFyX2NvdW50LCAyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgiKCIsIHJhbmdlX2NvdW50WzFdLCAiLCAiLCByYW5nZV9jb3VudFsyXSwgIikiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAocm91bmQoc3BhcnNpdHlfY291bnQsIDQpICogMTAwLCAiJSIpKSkKICAjIGNyZWF0ZSBjb3VudHMgaGlzdG9ncmFtCiAgcDAgPC0gZGF0YS5mcmFtZSh4ID0gYXMubnVtZXJpYyh6QGFzc2F5cyRvcmlnaW5hbGV4cEBjb3VudHMpKSAlPiUgCiAgICAgICAgZ2dwbG90KGFlcyh4ID0geCkpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJkb2RnZXJibHVlIikgKyAKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9zY2llbnRpZmljKCkpICsgCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfY29tbWEoKSkgKyAKICAgICAgICBsYWJzKHggPSAiUmF3IEV4cHJlc3Npb24iLCB5ID0gIkZyZXF1ZW5jeSIpICsgCiAgICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkKICAjIGNyZWF0ZSBsb2cgY291bnRzIGhpc3RvZ3JhbQogIHAxIDwtIGRhdGEuZnJhbWUoeCA9IGFzLm51bWVyaWMoekBhc3NheXMkb3JpZ2luYWxleHBAZGF0YSkpICU+JSAKICAgICAgICBnZ3Bsb3QoYWVzKHggPSB4KSkgKyAKICAgICAgICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gImZvcmVzdGdyZWVuIikgKyAKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9zY2llbnRpZmljKCkpICsgCiAgICAgICAgbGFicyh4ID0gIk5vcm1hbGl6ZWQgRXhwcmVzc2lvbiIsIHkgPSAiRnJlcXVlbmN5IikgKyAKICAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KQogICMgY3JlYXRlIFVNQVAgYnkgY2x1c3RlciAmIHN1YmplY3QKICBsZWdlbmRfY2x1c3QgPC0gZ2dwdWJyOjpnZXRfbGVnZW5kKHAgPSAoZGF0YS5mcmFtZShVTUFQMSA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVTUFQMiA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyID0geiRsYWJlbCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3Bsb3QoYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGNsdXN0ZXIpKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9tX3BvaW50KCkgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3NjaTo6Y2F0ZWdvcnkyMF9kMyIpWzE6bGVuZ3RoKHVuaXF1ZSh6JGxhYmVsKSldKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJzKGNvbG9yID0gIkxvdXZhaW5cbkNsdXN0ZXIiKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSArIHRoZW1lKGxlZ2VuZC50ZXh0LmFsaWduID0gMC41KSkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogIGxlZ2VuZF9zdWJqIDwtIGdncHVicjo6Z2V0X2xlZ2VuZChwID0gKGRhdGEuZnJhbWUoVU1BUDEgPSB6QHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3NbLCAxXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVTUFQMiA9IHpAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5nc1ssIDJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YmplY3QgPSB6JHN1YmplY3QpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3Bsb3QoYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IHN1YmplY3QpKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmNhdGVnb3J5MjBfZDMiKVsobGVuZ3RoKHVuaXF1ZSh6JGxhYmVsKSkgKyAxKToyMF0pICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFicyhjb2xvciA9ICJTdWJqZWN0IikgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSkpCiAgcDJhIDwtIGRhdGEuZnJhbWUoVU1BUDEgPSB6QHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3NbLCAxXSwgCiAgICAgICAgICAgICAgICAgICAgVU1BUDIgPSB6QHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3NbLCAyXSwgCiAgICAgICAgICAgICAgICAgICAgY2x1c3RlciA9IHokbGFiZWwsIAogICAgICAgICAgICAgICAgICAgIHN1YmplY3QgPSB6JHN1YmplY3QpICU+JSAKICAgICAgICAgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKGNvbHMgPSBjKGNsdXN0ZXIsIHN1YmplY3QpLCBuYW1lc190byA9ICJpZGVudCIsIHZhbHVlc190byA9ICJpZGVudF92YWx1ZSIpICU+JSAKICAgICAgICAgICBtdXRhdGUoaWRlbnQgPSBjYXNlX3doZW4oaWRlbnQgPT0gImNsdXN0ZXIiIH4gIkxvdXZhaW4gQ2x1c3RlciIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIlN1YmplY3QiKSkgJT4lIAogICAgICAgICAgIGdncGxvdChhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gaWRlbnRfdmFsdWUsIGdyb3VwID0gaWRlbnQpKSArIAogICAgICAgICAgIGZhY2V0X3dyYXAofmlkZW50KSArIAogICAgICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmNhdGVnb3J5MjBfZDMiKSkgKyAKICAgICAgICAgICBsYWJzKHggPSAiVU1BUCAxIiwgeSA9ICJVTUFQIDIiKSArIAogICAgICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCAKICAgICAgICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpCiAgcDJiIDwtIChwMmEgKyAod3JhcF9lbGVtZW50cyhsZWdlbmRfY2x1c3QpIC8gd3JhcF9lbGVtZW50cyhsZWdlbmRfc3ViaikpICsgCiAgICAgICAgIHBsb3RfbGF5b3V0KG5jb2wgPSAyLCB3aWR0aHMgPSBjKDQsIDEpKSkKICAjIGNyZWF0ZSBQQ0Egb2YgY2VsbCBvcmRlcmluZyAKICBwM2EgPC0gZGF0YS5mcmFtZShQQzEgPSB6QHJlZHVjdGlvbnMkUENBQGNlbGwuZW1iZWRkaW5nc1ssIDFdLCAKICAgICAgICAgICAgICAgICAgICBQQzIgPSB6QHJlZHVjdGlvbnMkUENBQGNlbGwuZW1iZWRkaW5nc1ssIDJdLCAKICAgICAgICAgICAgICAgICAgICBjZWxsX3RpbWUgPSB6JGNlbGxfdGltZV9ub3JtZWQsIAogICAgICAgICAgICAgICAgICAgIHN1YmplY3QgPSB6JHN1YmplY3QpICU+JSAKICAgICAgICAgZ2dwbG90KGFlcyh4ID0gUEMxLCB5ID0gUEMyLCBjb2xvciA9IGNlbGxfdGltZSkpICsgCiAgICAgICAgIGZhY2V0X3dyYXAofnN1YmplY3QpICsgCiAgICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gcGFsZXR0ZWVyX2QoIndlc2FuZGVyc29uOjpaaXNzb3UxIikpICsgCiAgICAgICAgIGxhYnMoeCA9ICJQQyAxIiwgeSA9ICJQQyAyIiwgY29sb3IgPSAiVHJ1ZSBPcmRlcmluZyIpICsgCiAgICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgICAgIHRoZW1lKGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCkpCiAgcDNiIDwtIGRhdGEuZnJhbWUoUEMxID0gekByZWR1Y3Rpb25zJFBDQUBjZWxsLmVtYmVkZGluZ3NbLCAxXSwgCiAgICAgICAgICAgICAgICAgICAgUEMyID0gekByZWR1Y3Rpb25zJFBDQUBjZWxsLmVtYmVkZGluZ3NbLCAyXSwgCiAgICAgICAgICAgICAgICAgICAgY2VsbF90aW1lID0geiRjZWxsX3RpbWVfbm9ybWVkKSAlPiUgCiAgICAgICBnZ3Bsb3QoYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG9yID0gY2VsbF90aW1lKSkgKyAKICAgICAgIGdlb21fcG9pbnQoKSArIAogICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHBhbGV0dGVlcl9kKCJ3ZXNhbmRlcnNvbjo6Wmlzc291MSIpKSArIAogICAgICAgbGFicyh4ID0gIlBDIDEiLCB5ID0gIlBDIDIiLCBjb2xvciA9ICJUcnVlIE9yZGVyaW5nIikgKyAKICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgCiAgICAgICB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSkKICBwM2MgPC0gKHAzYSB8IHAzYikgKyAKICAgICAgICAgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiLCB3aWR0aHMgPSBjKDMsIDIpLCBuY29sID0gMikKICAjIHRhYmxlIG9mIHNpbXVsYXRpb24gcGFyYW1ldGVycyAKICBwYXJhbV9kZiA8LSBkYXRhLmZyYW1lKG1ldHJpYyA9IGMoIk4gQ2VsbHMiLCAiTiBHZW5lcyIsICIlIER5bmFtaWMiLCAiQWxsb2NhdGlvbiIsICJOIFN1YmplY3RzIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBjKGFzLmNoYXJhY3RlcihuX2NlbGxzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKG5fZ2VuZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfZGVnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGxvY2F0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3N1YmplY3RzKSkKICBwbG90X3RhYmxlIDwtIHJiaW5kKHN1bW1hcnlfZGYsIHBhcmFtX2RmKSAlPiUgCiAgICAgICAgICAgICAgICBncmlkRXh0cmE6OnRhYmxlR3JvYihyb3dzID0gTlVMTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xzID0gYygiTWV0cmljIiwgIlZhbHVlIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUgPSBncmlkRXh0cmE6OnR0aGVtZV9taW5pbWFsKGNvcmUgPSBsaXN0KGZnX3BhcmFtcyA9IGxpc3QoaGp1c3QgPSAwLCB4ID0gMC4wNSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xoZWFkID0gbGlzdChmZ19wYXJhbXMgPSBsaXN0KGhqdXN0ID0gMCwgeCA9IDAuMDUpKSkpICU+JSAKICAgICAgICAgICAgICAgIGd0YWJsZTo6Z3RhYmxlX2FkZF9ncm9iKGdyb2JzID0gZ3JpZDo6cmVjdEdyb2IoZ3AgPSBncmlkOjpncGFyKGZpbGwgPSBOQSwgbHdkID0gMikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHQgPSAyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGIgPSBucm93KC4pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGwgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHIgPSBuY29sKC4pKSAlPiUgCiAgICAgICAgICAgICAgICBndGFibGU6Omd0YWJsZV9hZGRfZ3JvYihncm9icyA9IGdyaWQ6OnJlY3RHcm9iKGdwID0gZ3JpZDo6Z3BhcihmaWxsID0gTkEsIGx3ZCA9IDIpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByID0gbmNvbCguKSkKICAjIGFsaWduIGV2ZXJ5dGhpbmcgCiAgcDRhIDwtICgocDAgfCBwMSB8IHBsb3RfdGFibGUpICsgcGxvdF9sYXlvdXQod2lkdGhzID0gYygxLCAxLCAwLjUpKSkgLyAocDJiIHwgcDNjKSArIAogICAgICAgICBwbG90X2xheW91dChucm93ID0gMikgKyAKICAgICAgICAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gcGFzdGUwKCJNZXRyaWNzIGZvciBkYXRhc2V0OiAiLCBvYmpfbmFtZSkpCiAgIyBzYXZlICYgcHJpbnQgcGxvdAogIGdnc2F2ZShmaWxlbmFtZSA9IHBhc3RlMCgiUUNfIiwgb2JqX25hbWUsICIucGRmIiksCiAgICAgICAgIHBsb3QgPSBwNGIsIAogICAgICAgICBkZXZpY2UgPSAicGRmIiwgCiAgICAgICAgIHBhdGggPSAiL2JsdWUvcmJhY2hlci9qLmxlYXJ5L3JlcG9zL3NjTEFORV9BbmFseXNpcy9GaWd1cmVzL1FDX1Bsb3RzLyIsIAogICAgICAgICB3aWR0aCA9IDEzLAogICAgICAgICBoZWlnaHQgPSA5LCAKICAgICAgICAgdW5pdHMgPSAiaW4iLCAKICAgICAgICAgZHBpID0gInJldGluYSIpCiAgcHJpbnQocDRhKQogICMgY2xlYW51cCAKICBzaW5rKHRlbXBmaWxlKCkpCiAgcm0ocDAsIHAxLCBwMmEsIHAyYiwgcDNhLCBwM2IsIHAzYywgcDRhLCBwbG90X3RhYmxlKTsgZ2MoZnVsbCA9IFRSVUUpCiAgc2luaygpCn0pCnJtKG9ial9saXN0KQpgYGAKCiMgU2Vzc2lvbiBJbmZvIAoKYGBge3J9CnNlc3Npb25pbmZvOjpzZXNzaW9uX2luZm8oKQpgYGAK